C程序的内存布局

C memory layout of a program

我正在熟悉 C,我正在检查程序的内存布局。 我确实设法获得了一些关于变量在内存中的位置的信息,但我仍然有一些不清楚。

我 运行 在 lubuntu 18.04 运行 virtualbox 上 windows 10 作为主机

假设我有以下程序:

int foo(); // (??)
void point(void* p); // (??)
int data1; // bss segment
int data2 = 3; // data segment
int main(){
  char str[3] = {'a','b','c'}; // text segment(??)
  char *str1 = "word"; // str1 - stack, *str1 = 'w' - text segment (??)

  point(&data1); // call stack

  return 0;
}

void point(void* p){

 long dist1 = (size_t)&data2 - (size_t)p; // call stack - inside point's AF: dist1, p
 printf("%ld\n", dist1); /* is a long integer generally enough to hold addresses (or difference of 
                           addresses). is it legal to calculate differences of addresses of different 
                           segments? */  

}

int foo(){
  return 0;
}

对于我在评论中添加的问题,我需要一些帮助,并且可能需要一些对我已经做过的事情的确认。

提前致谢。

这完全是实现定义的。

关于地址,大多数系统提供intptr_tuintptr_t,它们是适合存放地址的整数类型。你可以用它做任何事情,因为它只是一个整数,但如果你将它转换回指针,它的有效性取决于你(即转换为 intptr_t 并返回就可以了,就像执行等价的正确指针算术,但仅此而已)。

还有ptrdiff_t,保存指针差;它通常与 intptr_t 相同,但始终可用。你仍然可以用它做任何事情,因为它是一个整数;操作意义由你决定。但是只有指针指向同一个数组的不同元素时才允许减去指针本身。 1

关于部分,函数体进入 .text 或等效文件;函数头本身不会去任何地方,但是如果函数被导出(Linux 上的默认值),链接器将必要的数据添加到程序文件中,动态 linker/loader 使用它来构建真正的跳转表。局部变量进入线程堆栈。常量可能位于 datarodata 甚至 text 中(尤其是在 x64 上)。 bss 是一个非存储部分,即在文件中,只有它的描述存在,但在程序启动时它被分配并且(通常)用零填充。

让我们用 gcc -O0 program.c -o program 编译你的程序,然后用 objdump -D program 反汇编它。为方便起见,我继续这样做 (AT&T syntax, Intel syntax)。您可以看到 foo.text 部分中,它已被替换为一个存根,该存根实际上什么也不做,只是 returns 在函数之外。由于 point 是在声明之后定义的,它相当于在声明中定义,并且也在 .text 部分中具有实际实现。可以看到你是对的,data1.bssdata2.data。至于你在main中的{'a', 'b', 'c'}数组,你可以看到它有点奇怪。

 6c1:   c6 45 f5 61             mov    BYTE PTR [rbp-0xb],0x61
 6c5:   c6 45 f6 62             mov    BYTE PTR [rbp-0xa],0x62
 6c9:   c6 45 f7 63             mov    BYTE PTR [rbp-0x9],0x63

这些值实际上被一个一个地加载到数组中,所以我猜你可以说它被存储在 .text 部分。您可能会注意到 "word" 字符串实际上并不在反汇编中。但是,如果您执行 readelf -x .rodata program,您会发现它位于 .rodata 部分。

Hex dump of section '.rodata':
  0x000007d0 01000200 776f7264 00256c64 0a00     ....word.%ld..

您还可以看到,虽然变量未按名称引用,但它们位于函数的 堆栈帧 中,由基指针的偏移量给出 rbp.对于 64 位二进制文​​件,一个地址是 8 个字节,对于 32 位二进制文​​件,一个地址是 4 个字节。