全局指针变量如何存储在内存中?

假设我们有一个简单的代码:

int* q = new int(13);

int main() {
    return 0;
}

显然,变量q是全局的并且已初始化。 从这个答案中,我们希望q变量存储在程序文件中的初始化数据段(.data)中 ,但它是一个指针,所以它的值(它是堆段中的地址)在运行时确定。 那么程序文件中存储在数据段中的值是什么?

我的尝试:
在我看来,编译器为数据段中的变量q (通常为8个字节,用于64位地址)分配了一些空间,没有任何有意义的值。 然后,在main函数代码之前在文本段中放置一些初始化代码以在运行时初始化q变量。 汇编中的这样的东西:

     ....
     mov  edi, 4
     call operator new(unsigned long)
     mov  DWORD PTR [rax], 13  // rax: 64 bit address (pointer value)

     // offset : q variable offset in data segment, calculated by compiler
     mov  QWORD PTR [ds+offset], rax // store address in data segment
     ....
main:
     ....

任何想法?


是的,这基本上是如何工作的。

请注意,在ELF中.data.bss.text实际上是部分,而不是段。 你可以通过运行你的编译器来查看程序集:

c++ -S -O2 test.cpp

您通常会看到一个main函数,以及该函数之外的某种初始化代码。 程序入口点(C ++运行时的一部分)将调用初始化代码,然后调用main 。 初始化代码也负责运行构造函数之类的东西。


int *q将进入.bss而不是.data部分,因为它只在运行时由非常量初始化器初始化(所以这只在C ++中合法,而不是在C中)。 对于它,可执行文件的数据段中不需要有8个字节。

编译器通过将其地址放入CRT(C运行时间)启动代码在调用main之前调用的初始化程序数组中来安排初始化函数的运行。

在Godbolt编译器资源管理器中,你可以看到init函数的asm没有所有的指令噪声。 注意寻址模式只是一个简单的RIP相对访问q 。 链接器在这一点上填充了RIP的右侧偏移量,因为即使.text.bss段最终在单独的段中,链接时间常量也是如此。

Godbolt的编译器噪声过滤对我们来说并不理想。 一些指令是相关的,但其中许多不是。 下面是一个手动选择的gcc6.2 -O3 asm输出和Godbolt的“过滤器指令”选项混合使用,只需要int* q = new int(13); 声明。 (不需要同时编译一个main文件,我们没有链接一个可执行文件)。

# gcc6.2 -O3 output
_GLOBAL__sub_I_q:      # presumably stands for subroutine
    sub     rsp, 8           # align the stack for calling another function
    mov     edi, 4           # 4 bytes
    call    operator new(unsigned long)   # this is the demangled name, like from objdump -dC
    mov     DWORD PTR [rax], 13
    mov     QWORD PTR q[rip], rax      # clang uses the equivalent `[rip + q]`
    add     rsp, 8
    ret

    .globl  q
    .bss
q:
    .zero   8      # reserve 8 bytes in the BSS

没有提及ELF数据(或任何其他)的基础。

也肯定没有段寄存器覆盖。 ELF段与x86段无关。 (无论如何,默认段寄存器是DS ,所以编译器不需要发出[ds:rip+q]或任何其他内容。有些反汇编可能是显式的,并且显示DS,即使在DS上没有段覆盖前缀指令,但是。)


这是编译器安排它在main()之前被调用的方式:

    # the "aw" sets options / flags for this section to tell the linker about it.
    .section        .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I_q       # this assembles to the absolute address of the function.

CRT开始代码有一个循环,它知道.init_array部分的大小,并.init_array在每个函数指针上使用内存间接call指令。

.init_array节标记为可写,因此它会进入数据段。 我不确定写什么。 也许CRT代码将它标记为已经完成了,并在调用它们之后调零指针?


在Linux中有一个用于在动态链接库中运行初始化程序的类似机制,这是由ELF解释程序在进行动态链接时完成的。 这就是为什么你可以调用printf()或从其他glibc的标准输入输出功能_start从手写ASM创建一个动态链接的二进制文件,为什么在一个静态链接二进制失败,如果你不调用正确的初始化函数。 (有关构建静态或动态二进制文件的更多信息,请参阅此Q&A,这些二进制文件定义了自己的_startmain() ,带有或不带有libc)。

链接地址: http://www.djcxy.com/p/82497.html

上一篇: How global pointer variables are stored in memory?

下一篇: Address of a variable in C program