【问题标题】:Why does GCC store global and static int differently?为什么 GCC 以不同方式存储全局和静态 int?
【发布时间】:2018-01-23 05:55:46
【问题描述】:

这是我的 C 程序,其中包含一个静态变量、两个全局变量、一个本地变量和一个外部变量。

#include <stdio.h>

int gvar1;
int gvar2 = 12;
extern int evar = 1;
int main(void)
{
    int lvar;
    static int svar = 4;
    lvar = 2;
    gvar1 = 3;
    printf ("global1-%d global2-%d local+1-%d static-%d extern-%d\n", gvar1, gvar2, (lvar+1), svar, evar);
return 0;
}

请注意,gvar1、gvar2、evar、lvar 和 svar 都定义为整数。

我使用 objdump 反汇编了代码,debug_str 显示如下:

Contents of section .debug_str:
 0000 76617269 61626c65 732e6300 6c6f6e67  variables.c.long
 0010 20756e73 69676e65 6420696e 74002f75   unsigned int./u
 0020 73657273 2f686f6d 6534302f 72616f70  sers/home40/raop
 0030 2f626b75 702f6578 616d706c 65730075  /bkup/examples.u
 0040 6e736967 6e656420 63686172 00737661  nsigned char.sva
 0050 72006d61 696e006c 6f6e6720 696e7400  r.main.long int.
 0060 6c766172 0073686f 72742075 6e736967  lvar.short unsig
 0070 6e656420 696e7400 67766172 31006776  ned int.gvar1.gv
 0080 61723200 65766172 00474e55 20432034  ar2.evar.GNU C 4
 0090 2e342e36 20323031 31303733 31202852  .4.6 20110731 (R
 00a0 65642048 61742034 2e342e36 2d332900  ed Hat 4.4.6-3).
 00b0 73686f72 7420696e 7400               short int.

为什么会显示如下?

unsigned char.svar
long int.lvar
short unsigned int.gvar1.gvar2.evar

GCC 如何决定它应该存储为哪种类型?

我正在使用 GCC 4.4.6 20110731 (Red Hat 4.4.6-3)

【问题讨论】:

  • ... 可能它们只是符号表或一些调试信息,与变量的类型无关。尤其是当它们似乎以相反的字典顺序排序时。
  • 这些点中的大多数都具有误导性。如果您查看十六进制代码,很少有真正的点字符0x2e。大多数是NUL 终结者0x00。例如,在序列unsigned char.svar.main. 中,所有点都是NUL 终结符。这意味着这是三个不相关的字符串。
  • 您能解释一下为什么数据段的布局对您很重要吗?您提出问题的动机是什么?
  • 诶?这不是程序的反汇编,它是某个对象或调试文件的十六进制转储。为了使用 objdump 我相信你必须给它实际的可执行文件。许多工具链提供了通过调试器进行反汇编的选项,例如通过 gdb。改用它。

标签: c gcc objdump


【解决方案1】:

为什么显示如下?

简单的答案:它不是在显示你的想法,而是在显示:

1 "variables.c"
2 "long unsigned int"
2a "unsigned int"
2b "int"
3 "/users/home40/raop/bkup/examples"
4 "unsigned char"
4a "char"
5 "svar"
6 "main"
7 "long int"
8 "lvar"
9 "short unsigned int"
10 "gvar1"
11 "gvar2"
12 "evar"
13 "GNU C 4.4.6 20110731 (Red Hat 4.4.6-3)"
14 "short int"

该部分命名为.debug_str;它包含一个由 NUL 字节分隔的字符串列表。这些字符串按任意顺序,并由.debug_info 部分引用。所以svar 关注unsigned char 这一事实毫无意义

.debug_info 部分包含实际的调试信息。本节不包含字符串。相反,它将包含如下信息:

    ...
Item 123:
    Type of information: Data type
    Name: 2b /* String #2b in ".debug_str" is "int" */
    Kind of data type: Signed integer
    Number of bits: 32
    ... some more information ...
Item 124:
    Type of information: Global variable
    Name: 8 /* "lvar" */
    Data type defined by: Item 123
    Stored at: Address 0x1234
    ... some more information ...
Begin item 125:
    Type of information: Function
    Name: 6 /* "main" */
    ... some more information ...
Item 126:
    Type of information: Local variable
    Name: 5 /* "svar" */
    Data type defined by: Item 123
    Stored at: Address 0x1238
    ... some more information ...
End item 125 /* Function "main" */
Item 127:
    ...

您可以使用以下命令查看此信息:

readelf --debug-dump filename.o

为什么 GCC 以不同方式存储全局和静态 int?

我对您的示例进行了两次编译:一次进行了优化,一次没有进行了优化。

未经优化,svargvar1 的存储方式完全相同相同:数据类型 int,存储在固定地址上。 lvar 是:数据类型 int,存储在堆栈中。

经过优化,lvarsvar 的存储方式相同:数据类型:int,根本不存储,而是将它们视为常量值。

(这是有道理的,因为这些变量的值永远不会改变。)

【讨论】:

    【解决方案2】:

    C11 规范(阅读 n1570) - 或较旧的 C 标准 - 没有定义存储全局或静态变量的地址或偏移量,因此实现(您的 gcc 编译器和您的 ld 链接器) 可以随意放置在任何地方。

    data segments 的组织和布局是一个实现细节。

    您可能需要阅读有关DWARF 的更多信息以了解调试信息,这对gdb 调试器很有用。

    如果您想了解linkers and loadersELF 格式的更多信息,您可能想了解它们是如何工作的。在 Linux 上,有几个实用程序可以检查 elf(5) 文件,包括 objdump(1)readelf(1)nm(1)

    请注意,您的 GCC4.4 是过时的旧版 GCC。当前版本为GCC7,GCC8 将在几周后发布(2018 年春季)。我强烈建议升级你的编译器。

    如果您需要了解数据段以这种方式组织的方式和原因以及您的实现选择这种布局的原因,您可以利用gccld(来自binutils)都是@987654333 @,并详细研究它们的来源。您需要多年的工作,因为它们是复杂的软件(超过一千万行源代码)。

    如果您碰巧开始研究GCC 的内部结构,请务必研究最新版本。 GCC 社区的大多数人可能已经忘记了 GCC4.4(2009 年发布)的细节。自从那件古老的事情以来,GCC 发生了很多变化。几年前,我写了很多关于 GCC 内部的幻灯片,请参阅documentation of GCC MELT

    顺便说一句,数据段或其中变量的布局可能会随optimization options 而变化。 lvar 可能不在内存中(例如,仅在寄存器中);可能会删除静态变量(使用类似as-if rule)等。

    对于单个translation unitfoo.c,您可以使用gcc -fverbose-asm -S -O foo.c 将其编译成汇编代码并查看发出的foo.s 汇编代码。

    要了解更多ld 链接器的工作原理,您可以查看一些相关的linker script。您可以通过在编译和链接命令中使用gcc -v(而不是gcc)从gcc 调用ld

    在大多数情况下,您不应该关心全局或静态的特定偏移量(在 object filesexecutables 中)或地址(在 processvirtual address space 中)变量。还要注意ASLRproc(5) 文件系统可用于了解您的进程。

    (您的问题严重缺乏动机和背景)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-12
      • 1970-01-01
      • 2016-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-20
      • 2015-10-04
      相关资源
      最近更新 更多