【问题标题】:comparison of array and pointer version in terms of memory allocation数组和指针版本在内存分配方面的比较
【发布时间】:2013-08-30 17:10:21
【问题描述】:

当我声明以下内容时,幕后会发生什么:-

char a[]="June 14";

char *a="June 14";

我的意思是如何为上述两个声明(如 stack 、 data segment 等等)分配内存。

【问题讨论】:

  • 这些声明是在文件范围(在任何函数之外)还是在块范围(在函数内部)进行的?
  • 前者在你的进程中有两个字符串副本;一个在只读数据段中,另一个 (a[]) 是在声明 a 的任何范围内的可写副本。后者有一个字符串的副本,并且它在一个只读段中,由a 指向。
  • @WhozCraig:这取决于它们出现的位置。如果它们是在文件范围内声明的,那么前者只需要字符串的一份副本。
  • @EricPostpischil 有趣。除了在任何远程现代 C/C++ 编译中的只读段之外,我从未在任何地方看到常量文字(无论它们是否需要)。我将不得不调试 rt-lib 启动代码以查看该初始化会发生什么。我现在很好奇。
  • @WhozCraig:在文件范围内的char []="June 14"; 中,字符串不是常量;它只是数组的初始数据,通常出现在数据段(不是只读数据段)中。在块范围内,编译器通常会将字符串的副本放在只读数据段中,并在每次执行到达声明时使用该副本来初始化堆栈上新创建的数组。

标签: c arrays string pointers memory


【解决方案1】:

以下是典型的 C 实现。

在文件范围内:

char a[] = "June 14"; 定义了一个最初包含“June 14”(包括终止空值)的数组。这些字符被放置在可执行文件的数据部分中,该数据部分在程序开始执行时加载到内存中。 (注意:加载可能是虚拟的,因为它实际上是程序虚拟地址空间的一部分,即使它没有立即物理读入内存。)

char *a = "June 14"; 定义了两件事。第一个是包含“June 14”的字符串。这些字符被放入可执行文件的数据部分。它可能是只读(也称为常量)数据段。第二个是指针。指针也可能放置在数据段中(不是只读的)。指针用字符串的地址初始化。 (根据程序在目标系统上的链接方式,可执行文件可能包含有关如何在加载时填写地址的指令,而不是包含实际地址本身。)

在块范围内

char a[] = "June 14"; 定义了一个最初包含“June 14”的数组。但是,每次执行到达声明时都必须创建和初始化此数组。 (在文件范围声明中,数组只需要在程序启动时进行初始化。)为了完成这个初始化,编译器将“June 14”放入可执行文件的数据段(同样可能是只读数据段) )。每当执行到达声明(或即将到达)时,编译器就会在堆栈上分配一些空间,并将只读副本中的字符复制到堆栈上的新空间中。

char *a = "June 14"; 类似于文件范围声明,因为字符串被放入数据段(可能是只读的)并创建一个指针。但是,指针名义上是在堆栈上而不是在数据段中。 (我说“名义上”是因为优化很可能导致像这样的指针主要在处理器寄存器中,而不是实际上在堆栈上,或者甚至完全优化掉。)指针通常通过计算指令初始化来自其他信息的字符串的地址。 (例如,加载器可以将寄存器设置为它加载数据段开始的地址,因此指针初始值的计算将采用该地址并将数据段内字符串的偏移量添加到该地址。 )

【讨论】:

  • 非常有见地的回答!!
【解决方案2】:

考虑代码:

#include <stdio.h>

char a1[] = "June 14";
const char *a2 = "June 14";

void function(void)
{
    char b1[] = "June 14";
    const char *b2 = "June 14";
    printf("%-7s : %-7s : %-7s : %-7s\n", a1, a2, b1, b2);
    a1[6]++;
    b1[6]++;
    a2++;
    b2++;
    printf("%-7s : %-7s : %-7s : %-7s\n", a1, a2, b1, b2);
}

int main(void)
{
    function();
    function();
    return(0);
}

数组a1 被分配空间并在程序加载字符串时进行初始化。为指针a2 分配了空间,并且该指针被初始化为指向字符串的副本。我使用了const,因为你不能修改字符串文字;该值可能存储在程序的文本段中(不可写)。

数组b1在堆栈上分配空间,当函数被调用时,它用字符串初始化。这意味着,正如WhozCraigcomment 中指出的那样,每次调用该函数时,在某处都必须有一个用于初始化数组的字符串副本。为指针b2 分配了空间,并且该指针被初始化为指向字符串的副本。事实上,编译器可能会让a2b2 指向同一个字符串。

函数打印四个字符串的值;然后它修改两个数组中数字的最后一位,并增加两个指针以指向字符串常量中的下一个字符,并再次打印四个字符串。对该函数的第二次调用将b1 重置为原始字符串("June 14"),但a1 在第二次调用开始时仍更改为"June 15"。同样,a2 仍然指向u 进行第一次打印,但n 进行第二次打印,而b2 指向J 第一个和u 第二个。因此输出为:

June 14 : June 14 : June 14 : June 14
June 15 : une 14  : June 15 : une 14 
June 15 : une 14  : June 14 : June 14
June 16 : ne 14   : June 15 : une 14 

如果函数内部有静态数组或静态指针,它们的行为类似于外部数组a1 和指针a2

【讨论】:

    【解决方案3】:

    这里有两件事情要分配空间。

    char a[]="June 14";
    char *a="June 14";
    

    对于对象“a”(指针)和常量数据“June 14”。

    如果“a”是在函数上下文中定义的——或——如果“a”是在静态上下文中定义的,则在堆栈上分配对象的空间。

    常量数据的空间在 .rodata(只读数据)部分中分配。

    【讨论】:

      【解决方案4】:

      对于,

      char a[]="6 月 14 日";

      Linux 上的 gcc 在堆栈区分配内存。发生以下转换,

      1. 为堆栈中的 a 分配内存,大小为 ("June 14") + 1
      2. 然后在我们调用函数时将“June 14”复制到内存区

      为了,

      char *b="6 月 14 日";

      Linux 上的 gcc 在 .rodata 中分配内存并将“June 14”字符串存储在 .rodata 部分中。然后分配变量 b,它保存 .rodata 部分中“June 14”字符串的内存区域的地址。

      查看示例 C 程序,

       #include<stdio.h>
      
       main()
       {
         char a[]="Test Message"; //memory is allocated in the stack and stores "Test Message"
         char *b="Test Const String"; //Memory allocated in .rodata section and b points to the memory address
       }
      

      我已经用 gcc 和 -c 选项编译了上面的代码,并使用 objdump 命令分析了 obj 文件。以下是输出, constPointer.o:文件格式elf32-i386

       Contents of section .text:
       0000 8d4c2404 83e4f0ff 71fc5589 e55183ec  .L$.....q.U..Q..
       **0010 24c745eb 54657374 c745ef20 4d6573c7  $.E.Test.E. Mes.
       0020 45f37361 6765c645 f700c745 f8000000  E.sage.E...E....**
       0030 0083c424 595d8d61 fcc3               ...$Y].a..
       Contents of section .rodata:
       **0000 54657374 20436f6e 73742053 7472696e  Test Const Strin
       0010 6700                                 g.**
       Contents of section .comment:
       0000 00474343 3a202847 4e552920 342e332e  .GCC: (GNU) 4.3.
       0010 30203230 30383034 32382028 52656420  0 20080428 (Red
       0020 48617420 342e332e 302d3829 00        Hat 4.3.0-8).
      

      我已经突出显示(使用**)在不同部分完成的内存分配。在那个“Test const String”被放置在不可修改的.rodata部分中。

      所以,a[3]='t';将编译没有任何错误或警告,但 b[2]='t';会导致运行时错误。

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 2014-08-14
        • 2015-12-10
        • 1970-01-01
        • 2011-04-29
        • 2013-07-02
        • 2013-04-03
        • 1970-01-01
        • 2012-03-15
        • 2015-06-19
        相关资源
        最近更新 更多