在内存分配方面比较数组和指针版本

时间:2013-08-30 17:10:22

标签: c arrays string pointers memory

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

char a[]="June 14";

char *a="June 14";

我的意思是如何为堆栈,数据段之类的上述两个声明分配内存。

4 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:3)

考虑代码:

#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在堆栈上分配空间,当调用该函数时,它会用字符串初始化。这意味着,正如WhozCraig中提到的comment一样,每次调用函数时,必须有某个字符串的副本用于初始化数组。指针b2被分配用于指针的空间,并且该指针被初始化为指向该字符串的副本。实际上,编译器可能会使a2b2指向相同的字符串。

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

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

答案 2 :(得分:0)

这里有两个空间分配空间。

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

对象“a”(指针)和常量数据“6月14日”。

如果在数据段中的函数--OR--的上下文中定义了“a”,则在堆栈上分配对象的空间,如果在静态上下文中定义了“a”。

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

答案 3 :(得分:0)

对,

  
    

char a [] =“6月14日”;

  
Linux上的gcc在堆栈区域中分配内存。发生转换后,

  
    
        
  1. 为大小为(“6月14日”)+ 1
  2. 的堆栈分配内存     
  3. 然后在我们调用函数
  4. 时将“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';将导致运行时错误。

希望这会有所帮助。