【问题标题】:char array and pointer initialization semanticschar 数组和指针初始化语义
【发布时间】:2015-03-21 14:52:46
【问题描述】:

在下面的代码sn-p

   char *str1 = "abcd";
   char str2[] = "defg";

我意识到第一条语句将指向字符串文字的指针存储在可执行文件的只读部分中,而第二条语句存储在读写部分中。 在检查生成的指令时,我验证第一个指令将 Rodata 部分中指向“abcd”的指针存储到 str1。

有趣的是第二个陈述。编译器插入代码以将值存储到

char *str1 = "abcd";
8048420:       c7 44 24 10 20 85 04    movl   $0x8048520,0x10(%esp)
8048427:       08
char str2[] = "defg";
8048428:       c7 44 24 17 64 65 66    movl   $0x67666564,0x17(%esp)
804842f:       67
8048430:       c6 44 24 1b 00          movb   $0x0,0x1b(%esp)

编译器如何决定何时执行以下操作?

  1. 字符串文字存储在rodata部分
  2. 字符串文字存储在数据段 (rw) 中
  3. 字符串内存隐含在堆栈中,生成指令填充堆栈?
  4. 是否还有其他可能性以及硬件之间的差异?

注意:我正在运行一个带有调试符号和 -O0 的精确 32 vagrant、gcc

【问题讨论】:

  • 恐怕你的问题有很多重复。

标签: c arrays linux pointers


【解决方案1】:

如果你的

char str2[] = "defg";

定义在函数内部,然后编译器将生成指令以将数据放入堆栈(忽略可能的优化,例如将值纯粹保存在寄存器中)。这与其他自动(堆栈)变量一样有效。

它还可以选择将数据从其他地方复制到堆栈,而不是例如将数据值作为指令的立即操作数。它可能会选择对更长的字符串执行此操作以避免代码膨胀。

不管编译器做什么,对 str2 内容的修改必须在下次调用该函数时不可见(就像其他自动变量一样)。

如果str2 是全局的(这给了它静态存储持续时间),那么数据将在读/写数据段中结束。如果您在函数内部提供数组静态存储持续时间,也会发生这种情况,如

static char str2[] = "defg";

使用字符串字面量初始化指针时,如

char *s = "defg";

,数据在只读数据段结束,指针本身如何用数据的地址初始化的规则同以上。

【讨论】:

  • 效果必须是函数本地的事实的解释并没有让我更早地感到震惊。它使其他一切都变得清晰。感谢您指出这一点。
  • @MIkhail:很容易错过。有例如由于这个原因,一个大的查找表作为函数内部的(非static)数组也可能不是一个好主意。您可能会得到代码来将数据复制到每次调用函数时运行的堆栈中。
  • 我猜一个编译器可能会优化这种情况,如果它可以告诉数组只被读取,但至少 GCC 没有(我猜其他编译器也没有)。
【解决方案2】:

当内存中的聚合对象用编译时聚合值(不限于字符串字面量)初始化时,编译器总是有选择的

  1. 在编译时在只读数据部分预先构建完整的初始化程序,然后在运行时使用memcpy将整个内容复制到可修改的目标值中。

  2. 生成将在运行时直接“就地”构建目标值的代码。

基本上,第一种是“基于数据”的方法,第二种是“基于代码”的方法。在您的情况下,编译器使用基于代码的解决方案,可能是因为文字很短。使用更长的文字,我怀疑它最终会切换到第一种方法。

可以想象,在某些情况下,某些编译器可能会使用混合方法:部分数据在某处预先构建,memcpy-ed 从那里编译,其余数据是即时构建的。

【讨论】:

  • 当我将第二个字符串更改为更长的字符串时,它确实将其存储在rodata部分并插入了复制它的代码,但那是memcpy还是只是复制指令?
  • @MIkhail:memcpy() 也只是一堆指令,可能有很多变化。编译器可以内联插入指令来执行复制,或者调用外部函数,但这只是一个实现细节。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-10
  • 1970-01-01
  • 1970-01-01
  • 2019-11-04
  • 1970-01-01
相关资源
最近更新 更多