【问题标题】:How separately declared and initialized variables work in memory?分别声明和初始化的变量如何在内存中工作?
【发布时间】:2017-07-17 16:00:01
【问题描述】:

我有一个关于计算机内存工作的问题。我试图自己得到答案,但我无法弄清楚它是如何工作的。所以,想象一下一个人声明一个指向未来字符串的指针的情况;但是,初始化会稍晚一点:

char *str; 

之后,他或她想要声明另一个变量。

char her;  

紧接着,两个变量都被初始化,它们的地址和值被打印到 STDOUT。整个程序是这样的:

int     main(void)
{
    char *str;
    char her;

    her = 'Y';
    str = "HelloMuraMana";
    printf("%p\n", (void *)&str);
    printf("%p\n", (void *)&(str[1]));
    printf("%p\n", (void *)&(str[2]));
    printf("%p\n", (void *)&(str[3]));
    printf("%p\n", (void *)&(str[4]));
    printf("%p\n", (void *)&her);
    return (0);
}    

现在,我的问题是:计算机如何为这两个变量(尤其是字符串字符)分配内存。我还想添加一张我的 macOS 机器结果显示给我的图片:

CLICK HERE to see the results

编辑:
我对这里的内存工作方式特别感兴趣。另外,请注意 str[0] 有一个地址,而 str1、str[2]、str[3] 和 str[4] 有其他的,与第一个元素地址不连续。

【问题讨论】:

  • 太宽泛了。请编译器/链接器为您生成映射文件并查看。
  • 这可能会有所帮助:What and where are the stack and heap?
  • 不要发布指向图片的链接 - 在问题的文本中包含结果。内存分配的细节因架构和编译器而异;你真的需要缩小你的问题范围以获得有用的答案。
  • 了解linkers
  • “单独声明和初始化的变量”不存在。变量只能在声明时初始化。任何其他语句都不是初始化。

标签: c arrays memory


【解决方案1】:

就像我在评论中所说,事物在内存中具体化的细节会因平台而异。但是,这里有一个非常高级的视图,应该可以对事物的工作方式有所了解。

首先,在 C 编程语言的上下文中,对象可以具有以下几个存储持续时间之一:staticautomaticallocatedthread local。具有static 存储期限的对象在程序启动时立即分配,并在程序退出时释放。具有automatic 存储持续时间的对象在进入其封闭范围(函数或块)时分配,并在该范围退出时立即释放。具有allocated 存储持续时间的对象通过调用malloc/calloc/realloc 分配,并通过调用free 释放。我不会进入thread local,因为它与这个讨论并不真正相关。

当你的程序加载到内存中时,它的布局是这样的(假设 x86 或类似的):

              +------------------------+
high address  | Command line arguments |   
              | and environment vars   |  
              +------------------------+
              |         stack          |  <-- str and her live here, but
              | - - - - - - - - - - -  |      only for the duration of main()
              |           |            |
              |           V            |
              |                        |
              |           ^            |
              |           |            |
              | - - - - - - - - - - -  |
              |          heap          |
              +------------------------+
              |    global and read-    | <-- "HelloMuraMana" lives here for 
              |       only data        |     duration of the program
              +------------------------+
              |     program text       |
 low address  |    (machine code)      |
              +------------------------+   

具体情况取决于您的系统。请注意,这是虚拟地址空间中的情况,而不是物理内存。

当您的程序运行时,从标记为stack 的区域分配auto 变量(函数参数和函数或块的局部变量,如herstr)的存储空间。 allocated 对象的存储空间是从标记为heap 的区域分配的。

static 对象的存储以及像"HelloMuraMana" 这样的字符串文字 的存储取自不同的段;在上图中,它将是标记为global and read-only data 的段。根据您的系统,字符串文字可能存储在只读段中(例如.rodata.rdata),也可能存储在可写段中。 假定字符串文字是不可变的(因此称为“文字”),因此尝试修改字符串文字将导致未定义的行为。

在上面的布局中,全局数据对象的地址将低于堆栈或堆对象,这在您的输出中显示。变量str是在输入main时从栈中分配的;它的值是字符串字面量的地址,它是在程序第一次启动时从全局数据段分配的。

【讨论】:

  • 感谢您非常详细的回答。但是,我的主要问题仍然完好无损 - 数组的第一个元素怎么可能与数组的其余部分(在堆栈中分配)没有连续的地址?我认为整个数组应该位于连续的内存单元中。
  • @kjioyh:您没有打印数组第一个元素的地址 (&amp;str[0]) - 您打印了 指针变量 str 的地址,这是与数组不同的对象。
【解决方案2】:

从概念上讲,当程序加载到内存中时,它有 3 个区域(段):

  • 代码段:你的程序的文本存储在这里(它是一个只读区域)
  • 数据段:包含任何具有预定义值且可以修改的全局或静态变量
  • 堆栈段:这里加载了调用函数。为每个函数调用压入堆栈的一组值(堆栈帧),其中包含函数的返回地址和局部变量。

在您的情况下,char her 变量是 main() 函数中的局部变量,它使用值 Y 初始化。因此,它存储在堆栈中并且可以修改。

char *str 变量是指向"HelloMuraMana\0" 地址的指针,"HelloMuraMana\0" 是位于代码段中的常量。作为一个只读区域,您不能修改它的内容。

【讨论】:

    【解决方案3】:

    你应该选择一个稍微简单的例子:

    int     main(void)
    {
        char *str;
        char her;
    
        her = 'Y';
        str = "HelloMuraMana";
        puts(str);
        putchar(her);
        return (0);
    }    
    

    compile it into assembly 并检查:

    .LC0:
            .string "HelloMuraMana"
    main:
            pushq   %rbp                     
            movq    %rsp, %rbp               //frame setup
            subq    $16, %rsp                //allocate 2*8bit words for the 2 variables
            movb    $89, -1(%rbp)            //initialize her
            movq    $.LC0, -16(%rbp)         //initialize str
            movq    -16(%rbp), %rax          //prepare the argument to puts
            movq    %rax, %rdi
            call    puts                     //self-descriptive
            movsbl  -1(%rbp), %eax           //prepare the argument to putchar
            movl    %eax, %edi
            call    putchar                  //self-descriptive
            movl    $0, %eax                 //prepare the main return value
            leave
            ret
    

    现在,从反汇编中可以看出,两个堆栈变量是通过堆栈指针减法分配的(subq $16, %rsp)(因为堆栈通常向下增长)。字符串字面量 char 数组是一个静态生命周期变量,它进入程序加载器将在程序加载时分配的段中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      相关资源
      最近更新 更多