转自:http://blog.csdn.net/hackerain/article/details/7953261

http://blog.csdn.net/firefly_2002/article/details/8045096

首先要来理解一下可执行文件加载进内存后形成的进程在内存中的结构,如下图:

代码区:存放CPU执行的机器指令,代码区是可共享,并且是只读的。

数据区:存放已初始化的全局变量、静态变量(全局和局部)、常量数据。

BBS区:存放的是未初始化的全局变量和静态变量。

栈区:由编译器自动分配释放,存放函数的参数值、返回值和局部变量,在程序运行过程中实时分配和释放,栈区由操作系统自动管理,无须程序员手动管理。

堆区:堆是由malloc()函数分配的内存块,使用free()函数来释放内存,堆的申请释放工作由程序员控制,容易产生内存泄漏。

c语言中的存储类型有auto, extern, register, static 这四种,存储类型说明了该变量要在进程的哪一个段中分配内存空间,可以为变量分配内存存储空间的有数据区、BBS区、栈区、堆区。下面来一一举例看一下这几个存储类型:

1. auto存储类型

auto只能用来标识局部变量的存储类型,对于局部变量,auto是默认的存储类型,不需要显示的指定。因此,auto标识的变量存储在栈区中。示例如下:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. auto int i=1; //显示指定变量的存储类型
  5. int j=2;
  6. printf("i=%d\tj=%d\n",i,j);
  7. return 0;
  8. }

2. extern存储类型

extern用来声明在当前文件中引用在当前项目中的其它文件中定义的全局变量。如果全局变量未被初始化,那么将被存在BBS区中,且在编译时,自动将其值赋值为0,如果已经被初始化,那么就被存在数据区中。全局变量,不管是否被初始化,其生命周期都是整个程序运行过程中,为了节省内存空间,在当前文件中使用extern来声明其它文件中定义的全局变量时,就不会再为其分配内存空间。

示例如下:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. int i=5; //定义全局变量,并初始化
  3. void test(void)
  4. {
  5. printf("in subfunction i=%d\n",i);
  6. }
[cpp] view plaincopy
  1. #include <stdio.h>
  2. extern i; //声明引用全局变量i
  3. int main(void)
  4. {
  5. printf("in main i=%d\n",i);
  6. test();
  7. return 0;
  8. }
[plain] view plaincopy
  1. $ gcc -o test test.c file.c  #编译连接
  2. $ ./test  #运行
[plain] view plaincopy
  1. 结果:
  2. in main i=5
  3. in subfunction i=5

3. register存储类型

声明为register的变量在由内存调入到CPU寄存器后,则常驻在CPU的寄存器中,因此访问register变量将在很大程度上提高效率,因为省去了变量由内存调入到寄存器过程中的好几个指令周期。如下示例:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. int main(void)
  3. {
  4. register int i,sum=0;
  5. for(i=0;i<10;i++)
  6. sum=sum+1;
  7. printf("%d\n",sum);
  8. return 0;
  9. }

4. static存储类型

被声明为静态类型的变量,无论是全局的还是局部的,都存储在数据区中,其生命周期为整个程序,如果是静态局部变量,其作用域为一对{}内,如果是静态全局变量,其作用域为当前文件。静态变量如果没有被初始化,则自动初始化为0。静态变量只能够初始化一次。示例如下:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. int sum(int a)
  3. {
  4. auto int c=0;
  5. static int b=5;
  6. c++;
  7. b++;
  8. printf("a=%d,\tc=%d,\tb=%d\t",a,c,b);
  9. return (a+b+c);
  10. }
  11. int main()
  12. {
  13. int i;
  14. int a=2;
  15. for(i=0;i<5;i++)
  16. printf("sum(a)=%d\n",sum(a));
  17. return 0;
  18. }
[plain] view plaincopy
  1. $ gcc -o test test.c
  2. $ ./test
  3. a=2,    c=1,    b=6 sum(a)=9
  4. a=2,    c=1,    b=7 sum(a)=10
  5. a=2,    c=1,    b=8 sum(a)=11
  6. a=2,    c=1,    b=9 sum(a)=12
  7. a=2,    c=1,    b=10    sum(a)=13

6. 字符串常量

字符串常量存储在数据区中,其生存期为整个程序运行时间,但作用域为当前文件,示例如下:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. char *a="hello";
  3. void test()
  4. {
  5. char *c="hello";
  6. if(a==c)
  7. printf("yes,a==c\n");
  8. else
  9. printf("no,a!=c\n");
  10. }
  11. int main()
  12. {
  13. char *b="hello";
  14. char *d="hello2";
  15. if(a==b)
  16. printf("yes,a==b\n");
  17. else
  18. printf("no,a!=b\n");
  19. test();
  20. if(a==d)
  21. printf("yes,a==d\n");
  22. else
  23. printf("no,a!=d\n");
  24. return 0;
  25. }
[plain] view plaincopy
  1. $ gcc -o test test.c
  2. $ ./test
  3. yes,a==b
  4. yes,a==c
  5. no,a!=d

总结如下表:

一、预备知识—程序的内存分配

C++编译器将计算机内存分为代码区和数据区,很显然,代码区就是存放程序代码,而数据区则是存放程序编译和执行过程出现的变量和常量。数据区又分为静态数据区、动态数据区以及常量区,动态数据区包括堆区和栈区。

以下是各个区的作用:
(1)代码区:存放程序代码;
(2)数据区
a.静态数据区: 在编译器进行编译的时候就为该变量分配的内存,即全局变量和静态变量(用static声明的变量),存放在这个区的数据程序全部执行结束后系统自动释放,声明周期贯穿于整个程序执行过程。全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域(.data),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(.bss)。
b.堆区:这部分存储空间完全由程序员自己负责管理,它的分配和释放都由程序员自己负责。这个区是唯一一个可以由程序员自己决定变量生存 期的区间。可以用malloc,new申请对内存,并通过free和delete释放空间。如果程序员自己在堆区申请了空间,又忘记将这片内存释放掉,就 会造成内存泄露的问题,导致后面一直无法访问这片存储区域。但程序退出后,系统自动回收资源。分配方式倒是类似于链表。
c.栈区:存放函数的形式参数和局部变量,由编译器分配和自动释放,函数执行完后,局部变量和形参占用的空间会自动被释放。效率比较高,但是分配的容量很有限。
d.常量区: 存放常量的区间,如字符串常量等,注意在常量区存放的数据一旦经初始化后就不能被修改。 程序结束后由系统释放。
在弄懂内存分配的问题过后,来看看函数调用的过程:
执行某个函数时,如果有参数,则在栈上为形式参数分配空间(如果是引用类型的参数则除外),继续进入到函数体内部,如果遇到变量,则按情况为变 量在不同的存储区域分配空间(如果是static类型的变量,则是在进行编译的过程中已经就分配了空间),函数内的语句执行完后,如果函数没有返回值,则 直接返回调用该函数的地方(即执行原点),如果存在返回值,则先将返回值进行拷贝传回,再返回执行远点,函数全部执行完毕后,进行退栈操作,将刚才函数内 部在栈上申请的内存空间释放掉。

说明:数据结构中的堆(n个元素的序列{k1,k2,...,kn}称之为堆,当且仅当满足以下条件时:(1)ki>=k2i且ki>=k2i+1或(2)ki<=k2i且ki<=k2i+1)

二、例子程序

void main()
{
char *p="abcdef"; //
cout<<p[2]<<endl;
cout<<*(p+2)<<endl;
printf("%d\n%d\n",&p[2],(p+2));
}
p[2]和*(p+2)都输出c,说明p[2]和(p+2)指向同一地址,&p[2],(p+2)输出结果一样

这是一个前辈写的,非常详细
//main.cpp
int a = 0;          // 全局初始化区

char *p1;         // 全局未初始化区

char s1[] = "abcdef"; //1) s1在静态区,"abcdef"无需额外存放,就是数组s1内部,总共占用一个串的内存
const char *p ="abcdef";//2)p在静态区,"abcdef",必须额外存放(在常量区,通常也在静态区),/总共占用一个指针,和一个串的内存

main()
{

int b;            // 栈区

char s[]="abcdef";//s是在栈区,但这时的“abcdef”应该是在栈区,因为这里'[]'操作符已经说明是动态分配了,所以肯定是堆区。

//“abcdef”到底在栈区还是堆区,没有搞清楚,还请高手指教,我认为在栈区

// 因为局部非静态变量,在函数运行的过程中初始化。这样,编译器无法预先把数据,存放到 s[] 中。于是,只好先存起来,然后再用。s[]中这份,你可以随便处理,预存的      //那份,你根本摸不清它在哪里,所以根本不能处理它,不能读,不能写。如果你可以读写了,那么编译器就不能保证初始化,是正确的了。

//而这个s指针没有被const* 修饰,所以指向的也不是常量,可以通过指针移动改变指向的内容。(初始化之前,"abcdef"在常量区,初始化之后在s[]有了一份,你可以随便处     //理s[],不知道对否)

*s='w';//正确

s[2]='w';//正确

char *p3 = "123456";     // p3在栈区,"123456/0" 在常量区,其值不能被修改,指向常量的指针是不可以通过指针移动去修改指针所指内容的

*p3='w';//错误,此区域是编译的时候确定的,并且程序结束的时候自动释放的, *p3= 'w';企图修改文字常量区引起错误

char *p2;         // 栈区

static int c =0;         // 全局(静态)初始化区
 
  p1 = (char *)malloc(10);
  p2 = (char *)malloc(20); // 分配得来的10和20字节的区域就在堆区
 
  strcpy(p1, "123456");    // "123456/0" 放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方
}

注意:

1)全局变量以及静态变量存放在静态数据区;

2)注意常量的存放区域,通常情况下,常量存放在程序区(程序区是只读的,因此任何修改常量的行为都是非法的),而不是数据区。有的系统,也将部分常量分配到静态数据区,比如字符串常量(有的系统也将其分配在程序区)。但是要记住一点,常量所在的内存空间都是受系统保护的,不能修改。对常量空间的修改将造成访问内存出错,一般系统都会提示。常量的生命周期一直到程序执行结束为止。

1.test1

#include<iostream>
using namespace std;void test(int *p)
{int b=2;p=&b;cout<<p<<endl;
}int main(void)
{int a=10;int *p=&a;cout<<p<<endl;test(p);cout<<p<<endl;return 0;
}
复制代码

第一行输出和第三行输出的结果相同,而第一行、第三行与第二行输出的结果不同。从这里可以看出,当指针作为参数进行传递时传递的也只是一个值,只不过该值只一个地址,因此对于形参的改变并不影响实参。

2.test2

#include<iostream>
using namespace std;char* test(void)
{char str[]="hello world!";return str;
}int main(void)
{char *p;p=test();cout<<p<<endl;return 0;
}
复制代码

输出结果可能是hello world!,也可能是乱麻。

出现这种情况的原因在于:在test函数内部声明的str数组以及它的值"hello world”是在栈上保存的,当用return将str的值返回时,将str的值拷贝一份传回,当test函数执行结束后,会自动释放栈上的空间,即存放hello world的单元可能被重新写入数据,因此虽然main函数中的指针p是指向存放hello world的单元,但是无法保证test函数执行完后该存储单元里面存放的还是hello world,所以打印出的结果有时候是hello world,有时候是乱麻。

3.test3

#include<iostream>
using namespace std;int test(void)
{int a=1;return a;
}int main(void)
{int b;b=test();cout<<b<<endl;return 0;
}
复制代码

输出结果为 1

有人会问为什么这里传回来的值可以正确打印出来,不是栈会被刷新内容么?是的,确实,在test函数执行完后,存放a值的单元是可能会被重写,但是在函数执行return时,会创建一个int型的零时变量,将a的值复制拷贝给该零时变量,因此返回后能够得到正确的值,即使存放a值的单元被重写数据,但是不会受到影响。

4.test4

#include<iostream>
using namespace std;char* test(void)
{char *p="hello world!";return p;
}int main(void)
{char *str;str=test();cout<<str<<endl;return 0;
}
复制代码

执行结果是 hello world!

同样返回的是指针,为什么这里会正确地打印出hello world1?这是因为char *p="hello world!",指针p是存放在栈上的,但是"hello world!”是一个常量字符串,因此存放在常量区,而常量区的变量的生存期与整个程序执行的生命期是一样的,因此在test函数执行完后,str指向存放“hello world!”的单元,并且该单元里的内容在程序没有执行完是不会被修改的,因此可以正确输出结果。

5.test5

#include<iostream>
using namespace std;char* test(void)
{char *p=(char *)malloc(sizeof(char)*100);strcpy(p,"hello world");return p;
}int main(void)
{char *str;str=test();cout<<str<<endl;return 0;
}
复制代码

运行结果 hello world

这种情况下同样可以输出正确的结果,是因为是用malloc在堆上申请的空间,这部分空间是由程序员自己管理的,如果程序员没有手动释放堆区的空间,那么存储单元里的内容是不会被重写的,因此可以正确输出结果。

6.test6

#include<iostream>
using namespace std;void test(void)
{char *p=(char *)malloc(sizeof(char)*100);strcpy(p,"hello world");free(p);if(p==NULL){cout<<"NULL"<<endl;}
}int main(void)
{test();return 0;
}
复制代码

没有输出

在这里注意了,free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重 要!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,释放内存后应把把指针指向NULL,防止指针在后面不小心又被使用,造成无法估计的后果

static全局变量与普通的全局变量有什么区别?

答:全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别在于作用域的扩展上。非静态的全局变量可以用extern扩展到组成源程序的多个文件中,而静态的全局变量的作用域只限于本文件,不能扩展到其它文件,由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。把全局变量改变为静态全局变量后是改变了它的作用域,限制了它的使用范围。

static局部变量和普通局部变量有什么区别?

答:把局部变量改变为静态局部变量后是改变了它的存储方式即改变了它的生存期。

static函数与普通函数有什么区别?

答:static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件   

综上所述:
static全局变量与普通的全局变量有什么区别:
static全局变量只初使化一次,防止在其他文件单元中被引用;   
static局部变量和普通局部变量有什么区别:
static局部变量只被初始化一次,下一次依据上一次结果值;   
static函数与普通函数有什么区别:
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
 
一个C语言变量分配的实际例子:
 
我们来看看在可执行文件中,变量会被分配在哪些区里.这里以可执行文件为例子,可执行文件有固定的内存加载地址,符号(函数/变量的名字)将来在内存里的地址连接器是可以提前确定的。
 
源程序编译连接的结果是形成1堆汇编指令代码,大致分为.text .data .bss等几个节区(section)。对于.exe文件和.so文件,全局和静态变量都放在.data 或.bss段(gas把源文件从头到尾扫描1遍,才知道一个变量的全部情况:是否定义;类型;是否初始化。然后把初始化的变量在.data段里分配位置和 空间,把没初始化的变量在.bss段里分配位置和空间,没定义的变量分配在.undef段)。汇编指令代码里全局变量表现为一个内存地址(全局变量在目标 文件里是一个偏移值,加载进内存里是一个内存地址)。临时变量在汇编代码里变成ebp/esp+n,表现为一个堆栈地址,化为程序正文(.text)的一 部分。有些变量的最终内存地址在加载进内存之前还不能确定,需要加载进内存才可以计算出来.
  全局变量 作用域是跨越多个源程序的。因此全局变量不能重名。静态变量作用域是位于单个源程序内。多个源程序可以有同名的全局静态变量。本例中,为了区分多个同名的静态变量,gcc 用 c444和c444.0 来加以区别。
 
[test@redhat]# more aaa.c
# include <stdio.h>
int a111 = 0;              # 全局变量 已初始化
char *p111 = "654321";     # 全局指针变量 已经初始化
static int c444 = 9;       # 静态全局变量 已经初始化
static int c555;           # 静态全局变量 未初始化
main() 

    int b222;              # 局部变量
    char s333[] = "abc";   # 局部变量
    char *p222;            # 局部变量
    char *p333 = "123456";    # 局部变量
    static int c444 =0;       # 已初始化静态局部变量,与前面静态全局变量重名
    p111 = (char *)malloc(10); 
    p222 = (char *)malloc(20);
    strcpy(p111, "123456");
}

[test@redhat]# gcc -o aaa ./aaa.c
 
[test@redhat]# readelf -a ./aaa
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x80482d0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          2040 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         7
  Size of section headers:           40 (bytes)
  Number of section headers:         27
  Section header string table index: 24
Section Headers:   Addr是文件加载进内存时,每个section在内存中的虚拟地址
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048114 000114 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048128 000128 000020 00   A  0   0  4
  [ 3] .hash             HASH            08048148 000148 00002c 04   A  4   0  4
  [ 4] .dynsym           DYNSYM          08048174 000174 000060 10   A  5   1  4
  [ 5] .dynstr           STRTAB          080481d4 0001d4 000053 00   A  0   0  1
  [ 6] .gnu.version      VERSYM          08048228 000228 00000c 02   A  4   0  2
  [ 7] .gnu.version_r    VERNEED         08048234 000234 000020 00   A  5   1  4
  [ 8] .rel.dyn          REL             08048254 000254 000008 08   A  4   0  4
  [ 9] .rel.plt          REL             0804825c 00025c 000018 08   A  4   b  4
  [10] .init             PROGBITS        08048274 000274 000017 00  AX  0   0  4
  [11] .plt              PROGBITS        0804828c 00028c 000040 04  AX  0   0  4
  [12] .text             PROGBITS        080482d0 0002d0 0001e4 00  AX  0   0 16
  [13] .fini             PROGBITS        080484b4 0004b4 00001b 00  AX  0   0  4
  [14] .rodata           PROGBITS        080484d0 0004d0 00001a 00   A  0   0  4
  [15] .eh_frame         PROGBITS        080484ec 0004ec 000004 00   A  0   0  4
  [16] .data             PROGBITS        080494f0 0004f0 00001c 00  WA  0   0  4
  [17] .dynamic          DYNAMIC         0804950c 00050c 0000c8 08  WA  5   0  4
  [18] .ctors            PROGBITS        080495d4 0005d4 000008 00  WA  0   0  4
  [19] .dtors            PROGBITS        080495dc 0005dc 000008 00  WA  0   0  4
  [20] .jcr              PROGBITS        080495e4 0005e4 000004 00  WA  0   0  4
  [21] .got              PROGBITS        080495e8 0005e8 00001c 04  WA  0   0  4
  [22] .bss              NOBITS          08049604 000604 000008 00  WA  0   0  4
  [23] .comment          PROGBITS        00000000 000604 000126 00      0   0  1
  [24] .shstrtab         STRTAB          00000000 00072a 0000ce 00      0   0  1
  [25] .symtab           SYMTAB          00000000 000c30 0004c0 10     26  2f  4
  [26] .strtab           STRTAB          00000000 0010f0 000275 00      0   0  1
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x004f0 0x004f0 R E 0x1000
  LOAD           0x0004f0 0x080494f0 0x080494f0 0x00114 0x0011c RW  0x1000
  DYNAMIC        0x00050c 0x0804950c 0x0804950c 0x000c8 0x000c8 RW  0x4
  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
  STACK          0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
 
Symbol table '.symtab' contains 76 entries:
对于.exe文件,符号的Value 是将来加载进内存中的真实地址;对于.so文件Value需要重定位.
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 08048114     0 SECTION LOCAL  DEFAULT    1 
     2: 08048128     0 SECTION LOCAL  DEFAULT    2 
     3: 08048148     0 SECTION LOCAL  DEFAULT    3 
     4: 08048174     0 SECTION LOCAL  DEFAULT    4 
     5: 080481d4     0 SECTION LOCAL  DEFAULT    5 
     6: 08048228     0 SECTION LOCAL  DEFAULT    6 
     7: 08048234     0 SECTION LOCAL  DEFAULT    7 
     8: 08048254     0 SECTION LOCAL  DEFAULT    8 
     9: 0804825c     0 SECTION LOCAL  DEFAULT    9 
    10: 08048274     0 SECTION LOCAL  DEFAULT   10 
    11: 0804828c     0 SECTION LOCAL  DEFAULT   11 
    12: 080482d0     0 SECTION LOCAL  DEFAULT   12 
    13: 080484b4     0 SECTION LOCAL  DEFAULT   13 
    14: 080484d0     0 SECTION LOCAL  DEFAULT   14 
    15: 080484ec     0 SECTION LOCAL  DEFAULT   15 
    16: 080494f0     0 SECTION LOCAL  DEFAULT   16 
    17: 0804950c     0 SECTION LOCAL  DEFAULT   17 
    18: 080495d4     0 SECTION LOCAL  DEFAULT   18 
    19: 080495dc     0 SECTION LOCAL  DEFAULT   19 
    20: 080495e4     0 SECTION LOCAL  DEFAULT   20 
    21: 080495e8     0 SECTION LOCAL  DEFAULT   21 
    22: 08049604     0 SECTION LOCAL  DEFAULT   22 
    23: 00000000     0 SECTION LOCAL  DEFAULT   23 
    24: 00000000     0 SECTION LOCAL  DEFAULT   24 
    25: 00000000     0 SECTION LOCAL  DEFAULT   25 
    26: 00000000     0 SECTION LOCAL  DEFAULT   26 
    27: 080482f4     0 FUNC    LOCAL  DEFAULT   12 call_gmon_start
    28: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    29: 080495d4     0 OBJECT  LOCAL  DEFAULT   18 __CTOR_LIST__
    30: 080495dc     0 OBJECT  LOCAL  DEFAULT   19 __DTOR_LIST__
    31: 080484ec     0 OBJECT  LOCAL  DEFAULT   15 __EH_FRAME_BEGIN__
    32: 080495e4     0 OBJECT  LOCAL  DEFAULT   20 __JCR_LIST__
    33: 080494f8     0 OBJECT  LOCAL  DEFAULT   16 p.0
    34: 08049604     1 OBJECT  LOCAL  DEFAULT   22 completed.1
    35: 08048320     0 FUNC    LOCAL  DEFAULT   12 __do_global_dtors_aux
    36: 08048360     0 FUNC    LOCAL  DEFAULT   12 frame_dummy
    37: 00000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    38: 080495d8     0 OBJECT  LOCAL  DEFAULT   18 __CTOR_END__
    39: 080495e0     0 OBJECT  LOCAL  DEFAULT   19 __DTOR_END__
    40: 080484ec     0 OBJECT  LOCAL  DEFAULT   15 __FRAME_END__
    41: 080495e4     0 OBJECT  LOCAL  DEFAULT   20 __JCR_END__
    42: 08048490     0 FUNC    LOCAL  DEFAULT   12 __do_global_ctors_aux
    43: 00000000     0 FILE    LOCAL  DEFAULT  ABS aaa.c
    44: 08049504     4 OBJECT  LOCAL  DEFAULT   16 c444   # static变量为LOCAL绑定属性(也即作用域) 已初始化静态变量存放在.data
    45: 08049508     4 OBJECT  LOCAL  DEFAULT   16 c444.0 # 已初始化静态变量存放在.data (多个源文件可以定义同名的静态变量)
    46: 08049608     4 OBJECT  LOCAL  DEFAULT   22 c555   # 未初始化静态变量存放在.bss
    47: 080494fc     4 OBJECT  GLOBAL DEFAULT   16 a111   # 全局变量为GLOBAL绑定属性  已初始全局变量存放在.data
    48: 0804950c     0 OBJECT  GLOBAL DEFAULT   17 _DYNAMIC
    49: 080484d0     4 OBJECT  GLOBAL DEFAULT   14 _fp_hw
    50: 080494f0     0 NOTYPE  GLOBAL HIDDEN  ABS __fini_array_end
    51: 080494f4     0 OBJECT  GLOBAL HIDDEN   16 __dso_handle
    52: 08048440    66 FUNC    GLOBAL DEFAULT   12 __libc_csu_fini
    53: 08048274     0 FUNC    GLOBAL DEFAULT   10 _init
    54: 0804829c   427 FUNC    GLOBAL DEFAULT  UND malloc@@GLIBC_2.0     55: 080482d0     0 FUNC    GLOBAL DEFAULT   12 _start
    56: 080494f0     0 NOTYPE  GLOBAL HIDDEN  ABS __fini_array_start
    57: 080483f0    71 FUNC    GLOBAL DEFAULT   12 __libc_csu_init
    58: 08049500     4 OBJECT  GLOBAL DEFAULT   16 p111
    59: 08049604     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
    60: 0804838c    89 FUNC    GLOBAL DEFAULT   12 main
    61: 080482ac   251 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC _
    62: 080494f0     0 NOTYPE  GLOBAL HIDDEN  ABS __init_array_end
    63: 080494f0     0 NOTYPE  WEAK   DEFAULT   16 data_start
    64: 080484b4     0 FUNC    GLOBAL DEFAULT   13 _fini
    65: 080494f0     0 NOTYPE  GLOBAL HIDDEN  ABS __preinit_array_end
    66: 08049604     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
    67: 080495e8     0 OBJECT  GLOBAL DEFAULT   21 _GLOBAL_OFFSET_TABLE_
    68: 0804960c     0 NOTYPE  GLOBAL DEFAULT  ABS _end
    69: 080494f0     0 NOTYPE  GLOBAL HIDDEN  ABS __init_array_start
    70: 080484d4     4 OBJECT  GLOBAL DEFAULT   14 _IO_stdin_used
    71: 080494f0     0 NOTYPE  GLOBAL DEFAULT   16 __data_start
    72: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    73: 080494f0     0 NOTYPE  GLOBAL HIDDEN  ABS __preinit_array_start
    74: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    75: 080482bc    48 FUNC    GLOBAL DEFAULT  UND strcpy@@GLIBC_2.0
 
 
文件./aaa加载进内存后,再看看变量的地址以及所在的区:
[test@redhat]# gdb ./aaa
GNU gdb 6.1.1
(gdb) disassemble main
Dump of assembler code for function main:
0x0804838c <main+0>:    push   %ebp
0x0804838d <main+1>:    mov    %esp,%ebp
0x0804838f <main+3>:    sub    $0x18,%esp
0x08048392 <main+6>:    and    $0xfffffff0,%esp
0x08048395 <main+9>:    mov    $0x0,%eax
0x0804839a <main+14>:   sub    %eax,%esp
# char s333[] = "abc";
0x0804839c <main+16>:   mov    0x80484df,%eax            # 0x80484df处为"abc",位于.rodata
0x080483a1 <main+21>:   mov    %eax,0xfffffff8(%ebp)     # 0xfffffff8(%ebp) 为局部变量 char s333[]
# char *p333 = "123456";
0x080483a4 <main+24>:   movl   $0x80484e3,0xfffffff0(%ebp)  # 0x80484e3处为"123456/0",位于.rodata; 0xfffffff0(%ebp) 为局部变量 char *p333
# p111 = (char *)malloc(10);
0x080483ab <main+31>:   sub    $0xc,%esp
0x080483ae <main+34>:   push   $0xa          # 0xa=10 ; push $0xa后,此时堆栈esp值又减去4字节,相当于sub $0x10,%esp
0x080483b0 <main+36>:   call   0x804829c <malloc>
0x080483b5 <main+41>:   add    $0x10,%esp
0x080483b8 <main+44>:   mov    %eax,0x8049500  # 0x8049500 为全局变量p111,位于.data
# p222 = (char *)malloc(20);
0x080483bd <main+49>:   sub    $0xc,%esp
0x080483c0 <main+52>:   push   $0x14        # 0x14=20 ; push $0xa后,此时堆栈esp值又减去4字节,相当于sub $0x10,%esp
0x080483c2 <main+54>:   call   0x804829c <malloc>
0x080483c7 <main+59>:   add    $0x10,%esp
0x080483ca <main+62>:   mov    %eax,0xfffffff4(%ebp)  # 0xfffffff4(%ebp) 为局部变量p222
# strcpy(p111, "123456");
0x080483cd <main+65>:   sub    $0x8,%esp
0x080483d0 <main+68>:   push   $0x80484e3    # 0x80484e3处内容为"123456/0",位于.rodata; 
0x080483d5 <main+73>:   pushl  0x8049500     # 0x8049500 为全局变量p111,位于.data
0x080483db <main+79>:   call   0x80482bc <strcpy>
0x080483e0 <main+84>:   add    $0x10,%esp
0x080483e3 <main+87>:   leave  
0x080483e4 <main+88>:   ret    
0x080483e5 <main+89>:   nop    
0x080483e6 <main+90>:   nop    
0x080483e7 <main+91>:   nop    
0x080483e8 <main+92>:   nop    
0x080483e9 <main+93>:   nop    
0x080483ea <main+94>:   nop    
0x080483eb <main+95>:   nop    
0x080483ec <main+96>:   nop    
0x080483ed <main+97>:   nop    
0x080483ee <main+98>:   nop    
0x080483ef <main+99>:   nop    
End of assembler dump.
(gdb) q

[test@redhat]#

三、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b
开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。
2.2申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈,是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活
2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
2.6存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#i nclude <stdio.h>;
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。
2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
堆和栈的区别主要分:
操作系统方面的堆和栈,如上面说的那些,不多说了。
还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。
虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因。

C/C++堆区、栈区、常量区、静态数据区、代码区详解相关推荐

  1. 自学计算机键盘基础知识,刘坚强办公学《新手学五笔打字》1-2 功能键区,电脑键盘功能基础知识按键详解...

    原标题:刘坚强办公学<新手学五笔打字>1-2 功能键区,电脑键盘功能基础知识按键详解 下面我们来详细认识电脑键盘的各个按键,先从最上面的功能键区开始,一共有16个键(如图1-2-1所示). ...

  2. java gc堆中的分区_jvm内存各个区域详解

    内存区域划分 Java虚拟机所管理的内存区域分为如下部分:方法区.GC堆.虚拟机栈.本地方法栈.PC程序计数器. 其中方法区.GC堆是所有线程共享的:虚拟机栈.本地方法栈.PC程序计数器是各个线程独占 ...

  3. C语言中可以用字符串常量来,C语言字符串详解

    原标题:C语言字符串详解 字符串是一种非常重要的数据类型,但是C语言不存在显式的字符串类型,C语言中的字符串都以字符串常量的形式出现或存储在字符数组中.同时,C 语言提供了一系列库函数来对操作字符串, ...

  4. 【JVM】运行时数据区介绍,程序计数器和虚拟机栈详解

    JVM越来越是Java面试中的重头戏,今天来总结一下JVM运行时数据区的相关内容. 文章目录 JVM运行时数据区 JVM运行时数据区内部结构 程序计数器(PC寄存器) 程序计数器的介绍 PC寄存器的实 ...

  5. java中常量final的用法_详解Java中final的用法

    本文主要介绍了Java中final的使用方法,final是java的关键字,本文就详细说明一下它的使用方法,需要的朋友可以参考下 概念 final 具有"不可改变的"的含义,可以修 ...

  6. php常量变量连接,PHP常量及变量区别原理详解

    常量: 用于储存一个不会变化也不希望变化的数据的标示符(命名规则与变量相同) 定义形式: 使用 define() 函数定义 使用形式:define("常量名" ,常量值) 使用 c ...

  7. char 赋值 加入变量_王牌编程语言Java常量、变量、数据类型详解

    IT技术研习社,专注互联网技术研究与分享,喜欢的朋友可以点击[关注]:把经验传递给有梦想的人: 什么是常量 常量指程序里持续不变的值,在整个程序运行过程中它是不可改变的常见的字面量类型有 整型字面常量 ...

  8. 堆(优先级队列)及TOPK问题详解

    文章目录 1.二叉树的顺序存储 1.1存储方式 1.2下标关系 2.堆的应用:优先级队列(默认小根堆) 2.1概念 2.2Java中优先级队列的简单介绍 3.Topk问题 3.1求N个数中前k个最大/ ...

  9. C++中的const成员函数(函数声明后加const,或称常量成员函数)用法详解

最新文章

  1. 数据库,傻逼才用外键约束!
  2. 最好用的枚举多选方法
  3. 二十年来一直没搞清楚的一个问题
  4. [python]---从java到python(03)---爬虫
  5. JAVA入门级教学之(什么是类加载)
  6. Alibaba之MySQL宝典_Alibaba之MySQL宝典流出!极致经典,堪称行业天花板
  7. X86工控机虚拟显示器设置(nomachine远程桌面)
  8. SmallPDF安装
  9. 甲骨文裁员,N+6 赔偿……部分员工不满
  10. java实现节假日维护_谈谈JAVA实现节假日验证
  11. zencart 模板文件说明
  12. mysql查询排名名次
  13. Excel 公式结果为0时不显示
  14. CS231n系列之 Lecture1:Introduction
  15. Tushare如何获取股票历史交易数据
  16. 7-5 手机号码 (20 分)
  17. iphone一键转移_iPhone 12换机首选,QQ同步助手一键智能备份迁移
  18. 你画我猜 计算机题目,你画我猜:你知道这些题目的答案是什么吗?
  19. python科学计算和可视化编程
  20. 这才是 玩转Github 的正确姿势

热门文章

  1. Uncaught (in promise)
  2. OAuth2扩展access_token(二)
  3. 如何做好微信公众号运营方案,需要做好这五点工作
  4. android 弹窗代码大全,36个Android开发常用经典代码大全
  5. 魔兽,极品飞车等等游戏在win7下不能全屏
  6. oracle中fn_getpy函数,SQL Server根据汉字笔划和取得拼音首字母进行排序
  7. GeoTools类打包后问题:
  8. StringBuilder StringBuffer 如何清空
  9. 曼达洛人对机器人的评价_为什么曼达洛人如此厌恶机器人?硬核奶爸原来也有童年阴影...
  10. python中upper函数有什么用_python里upper什么意思