【问题标题】:string literals and strcat字符串文字和 strcat
【发布时间】:2018-12-18 00:11:01
【问题描述】:

我不确定为什么 strcat 在这种情况下对我有用:

char* foo="foo";

printf(strcat(foo,"bar"));

它为我成功打印了“foobar”。

但是,根据之前在 stackoverflow 上讨论的主题:I just can't figure out strcat

它说,上面不应该工作,因为 foo 被声明为字符串文字。相反,它需要声明为缓冲区(一个预定大小的数组,以便它可以容纳我们试图连接的另一个字符串)。

那么,为什么上面的程序能成功地为我工作?

【问题讨论】:

  • 不保证失败。
  • 字符串文字是不可变的。你不能修改它。任何修改字符串文字的尝试都可能导致程序的未定义行为。当有 UB 时,您可以获得预期或任何意想不到的结果。
  • 出于好奇,您使用的是什么编译器/平台?较旧的编译器通常只是将字符串文字留在可写内存中(这 符合 标准,尽管是一个陷阱,因为修改它们仍然会导致“有趣”的结果),这可以解释这种行为。这同样适用于没有内存保护的平台上的当前编译器。
  • 即使foo 是可写的,它也不能被连接到。考虑尝试连接到char foo[] = "foo";。与char foo[42] = "foo"; 不同,没有可用内存。
  • @WeatherVane 阅读了这个问题。他正在链接到另一个问题,而这正是讨论的主题。所以这不是这个问题的主题。

标签: c string strcat


【解决方案1】:

此代码调用 Undefined Behavior (UB),这意味着您无法保证会发生什么(此处为失败)。

原因是字符串字面量是不可变的。这意味着它们是不可变的,任何这样做的尝试都会调用 UB。

请注意 UB 可能会出现什么困难的逻辑错误,因为它可能会工作(今天和在您的系统中),但它仍然是错误的,这使得您很可能会错过错误,并且相处得很好一切都很好。


PS:在这个 Live Demo 中,我很幸运遇到了 Segmentation fault。我说幸运,因为这个段错误会让我调查和调试代码。

值得注意的是,GCC 没有发出警告,而来自 Clang 的警告也无关紧要:

p

rog.c:7:8: warning: format string is not a string literal (potentially insecure) [-Wformat-security]
printf(strcat(foo,"bar"));
       ^~~~~~~~~~~~~~~~~
prog.c:7:8: note: treat the string as an argument to avoid this
printf(strcat(foo,"bar"));
       ^
       "%s", 
1 warning generated.

【讨论】:

  • 那是因为你应该使用char foo[255]="foo"; ;p
  • 因此;p。现在这么严重吗?
  • @JHBonarius 但这是另一个问题。所以这不是这个问题的主题。 ;p
【解决方案2】:

字符串字面量是不可变的,因为编译器将在您不会改变它们的假设下运行,而不是如果您尝试修改它们一定会出错。在法律术语中,这是“未定义的行为”,所以任何事情都可能发生,而且,就标准而言,这很好。

现在,在现代平台和现代编译器上,您确实有额外的保护:在具有内存保护的平台上,字符串表通常被放置在只读内存区域中,因此修改它会导致运行时错误。

不过,您可能有一个不提供任何运行时强制检查的编译器,或者是因为您正在为一个没有内存保护的平台进行编译(例如,80386 之前的 x86,所以几乎所有用于 DOS 的 C 编译器都如此作为 Turbo C,大多数微控制器在 RAM 而不是闪存上运行时,...),或者使用默认情况下不利用此硬件功能的旧编译器保持与旧版本(旧 VC++ 很长一段时间)的兼容,或者使用具有显式启用此类选项的现代编译器,再次与旧代码兼容(例如 gcc 与-fwritable-strings)。在所有这些情况下,您不会收到任何运行时错误是正常的。

最后,还有一个更诡异的极端情况:current-day optimizers actively exploit undefined behavior - 即他们假设它永远不会发生,并相应地修改代码。一个特别聪明的编译器生成的代码只是放弃这样的写入并非不可能,因为法律允许它在这种情况下做任何它最喜欢的事情。

这可以看一些简单的代码,比如:

int foo() {
    char *bar = "bar";
    *bar = 'a';
    if(*bar=='b') return 1;
    return 0;
}

这里,启用优化:

  • VC++ 看到 write 仅用于紧随其后的条件,因此将整个事情简化为 return 0;没有内存写入,没有段错误,它“似乎工作”(https://godbolt.org/g/cKqYU1);
  • gcc 4.1.2“知道”字面量不会改变;写入是多余的,它被优化掉了(所以,没有段错误),整个事情变成了return 1https://godbolt.org/g/ejbqDm);
  • 任何更现代的 gcc 选择更精神分裂的路线:写入不会被忽略(因此您会使用默认链接器选项获得段错误),但如果它成功(例如,如果您手动调整内存保护),您会得到一个return 1 (https://godbolt.org/g/rnUDYr) - 所以,内存被修改了,但后面的代码认为它没有被修改;这尤其令人震惊on AVR,没有内存保护并且写入成功。
  • clang does pretty much the same as gcc.

长话短说:不要碰运气,小心行事。始终将字符串文字分配给 const char *notchar *),让类型系统帮助您避免此类问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-15
    • 2017-01-24
    • 2020-08-19
    • 1970-01-01
    • 1970-01-01
    • 2019-01-15
    • 2016-03-14
    • 1970-01-01
    相关资源
    最近更新 更多