我一直在使用 IDE 调用编译器,而无需进行太多配置,但从选项中我可以看到,我的项目似乎设置为使用 gnu99 作为 C 语言标准,使用 gnu++11 作为 C++ 语言标准,同时使用 gcc 版本 4.2.1。
该项目正在物联网的嵌入式设备上运行,我需要模拟它的某些部分,而我懒惰地使用了 onlinegdb。在那里,我注意到我可能正在不安全地构建字符串,我担心该项目可能会受到损害并需要紧急更新。我很确定我设计的字符串大小永远不会被我传递给它们的数据溢出,但前提是我正确实现了它,而我恐怕没有这样做。
这是一个可运行的示例:
#include <stdio.h>
#include <string.h>
int main()
{
// example 1 works as expected initially - I think it is unsafe
char testString1[6];
sprintf(testString1, "123");
sprintf(testString1, "%s456", testString1);
printf("%s\n", testString1); // 123456
// example 2 doesn't work as I expected - I think it is intrinsically safe though
char testString2[6];
snprintf(testString2, 6, "123");
snprintf(testString2, 6, "%s456", testString2);
printf("%s\n", testString2); // 456
// example 3 what I found works and I think is safe (EDIT: I understand now it isn't after reading comments)
char testString3[6];
snprintf(testString3, 6, "123");
snprintf(testString3 + strlen(testString3), 6, "456");
printf("%s\n", testString3); // 123456
// example 4 modified example3 for safety
char testString4[6];
snprintf(testString4, 6, "123");
snprintf(testString4 + strlen(testString4), sizeof(testString4) - strlen(testString4), "4567");
printf("%s\n", testString4); // 12345
return 0;
}
这是输出:
main.c: In function ‘main’:
main.c:9:29: warning: ‘456’ directive writing 3 bytes into a region of size between 1 and 6 [-Wformat-overflow=]
9 | sprintf(testString1, "%s456", testString1);
| ^~~
main.c:9:5: note: ‘sprintf’ output between 4 and 9 bytes into a destination of size 6
9 | sprintf(testString1, "%s456", testString1);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.c:15:33: warning: ‘456’ directive output may be truncated writing 3 bytes into a region of size between 1 and 6 [-Wformat-truncation=]
15 | snprintf(testString2, 6, "%s456", testString2);
| ^~~
main.c:15:5: note: ‘snprintf’ output between 4 and 9 bytes into a destination of size 6
15 | snprintf(testString2, 6, "%s456", testString2);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123456
456
123456
12345
示例 1 是我到目前为止构建字符串的方式。不幸的是,我的 IDE/编译器没有警告我这一点,但 onlinegdb 却警告了我,可能是因为它使用了不同的编译器或语言标准。
示例 2 是我在注意到 onlinegdb 上的警告后第一次尝试使其更安全。我用 snprintf 替换了 sprintf,给了它一个大小限制,并继续用与示例 1 相同的模式填充它。这不起作用,只用新数据填充它。事实上,如果我删除示例 2 中额外的“456”字符,testString2 最终会被打印为空白。
示例 3 是我如何发现它非常有效并且我认为它是安全的。
我想问一下:
- 示例 1 安全吗?我认为这不是由于示例 1 和 2 的警告造成的。
- 为什么示例 2 没有按我的预期工作?
- 例3真的安全吗?我可以安全地开始构建这样的字符串吗?
编辑1:添加示例4。根据我的理解,snprintf第二个参数是写入字符串的最大字符数,而不是该字符串的最大字符数,因此为了更安全,我减去了字符串的长度(我假设它是分配给字符串的总大小中放置空字符的索引,现在它限制了正在写入的字符数,即使我尝试写入超出适合的字符数。
7
1 个回答
1
由于相同的原因,您的前两个示例是错误的:输出字符串与输入之一重叠。这样做会触发。
第 7.21.6.6p2 节中关于该函数明确提到了这一点sprintf
:
该
sprintf
函数等效于fprintf
,不同之处在于输出被写入数组(由参数指定)而不是流中。写入的字符末尾写入空字符;它不计入返回值的一部分。如果复制发生在重叠的对象之间,则行为未定义。
关于该功能的第 7.21.6.5p2 节中也有相同的语言snprintf
。
您的第三个示例不会遇到此问题,但是目标位置不够大,无法存储所需的字符串。它需要空间容纳您要输入的 6 个字符,以及终止空字节。
示例 4 之所以有效,是因为第二次调用snprintf
限制了附加到数组剩余长度的内容。
你实际上不需要sprintf
或snprintf
做你想做的事。您可以简单地使用strcpy
和strcat
:
char testString[7];
strcpy(testString, "123");
strcat(testString, "456");
您还可以使用strncat
它来snprintf
指定大小限制(在本snprintf
例中,应该是可用空间,而不是总空间)。还有该strncpy
函数,但不建议使用该函数,因为它不会在所有情况下自动以 null 终止目标字符串。
7
-
刚刚添加了示例 4,对示例 3 进行了修改,以限制添加到字符串中的字符数。这是有效的吗?我可以使用 strncat 并以任何方式指定限制,以确保它不会写入字符串之外吗?
– -
我明确建议不要使用 ,
strncpy
因为每个初学者都认为它是 的更安全版本strcpy
,就像strncat
的更安全版本一样strcat
。
– -
2@rmarques:使用
strncat()
更有可能导致错误而不是解决问题。躲开它。也避免使用strncpy()
,尽管它比 更容易正确使用strncat()
。注意:如果比字符串短,即使是空字符串,也会strncat(dst, sizeof(dst), “long string”)
越界。dst
dst
– -
@JonathanLeffler 至少
strncat()
保证放置一个 nul 终止符,但strncpy()
没有这样的保证。令人惊讶的是。
– -
@WeatherVane — 但与 不同的是,
strncpy()
当大小指定为 时,不会超出范围。两者都不令人满意或真正安全。两者都是危险的。但我拒绝使用,而我偶尔会使用,通常会在目标字符串的最后一个字节中随后分配一个空字节。sizeof(target)
strncpy()
strncat()
strncpy()
–
|
–
snprintf(testString2, 6, "%s456", testString2);
C 标准未定义的行为,因为 C 2018 7.21.6.5 2 表示“……如果在重叠的对象之间进行复制,则行为未定义。”您不应在sprintf
or的输出和输入中使用相同的数组snprintf
。–
-Wall -Wextra
到您的编译命令行。–
–
–
|