C++之内存管理

哈佛结构和冯诺依曼结构的主要区别就是处理器能不能实现取指令和取数据的并发进行。嵌入式芯片中主要是哈佛结构,PC机上是冯诺依曼结构。

经典的哈佛结构
程序存储器和数据存储器是各自独立的存储器。处理器应该有两套总线,一套是程序存储器的数据和地址总线,一套是数据存储器的数据和地址总线。取指令和取数据能并发进行。51的程序进程的逻辑代码段放在ROM中,而变量部分则放在RAM中,取ROM中的指令和RAM中的变量是两套总线。

改进型哈佛结构
程序存储器和数据存储器是各自独立的存储器。处理器只有一套总线,分时访问程序存储器和数据存储器,但是在处理器中有icache和dcache将程序和数据分开,所以处理器仍然可以并步执行取指令和取数据。从ARM9开始以后所有的ARM处理器内核都是改进型的哈佛结构。ARM的逻辑代码和变量都是存放在RAM中的,但是,它在内存中划分了两部分的空间,其中一部分放逻辑代码,另一部分存放变量,之间不会相互干扰。

冯诺依曼结构
没有程序存储空间和数据存储空间之分。处理器只有一套总线,取指令和取数据是不能同时进行的。程序进程全部在RAM中,他们之间一般是按照代码的执行顺序依次存储。由于全部在RAM中,运行速度快,所需的RAM多。

虚拟地址空间

每个程序运行时,操作系统都会为其分配内存,这个内存是以虚拟地址空间形式提供的。

内存分配方式有三种:  
1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多

堆栈段:

  1. 为函数内部的局部变量提供存储空间。

  2. 进行函数调用时,存储“过程活动记录”。

  3. 用作暂时存储区。如计算一个很长的算术表达式时,可以将部分计算结果压入堆栈。

数据段(静态存储区):

  包括BSS段(Block Started by Symbol)的数据段。BSS段存储未初始化或初始化为0的全局变量、静态变量,具体体现为一个占位符,并不给该段的数据分配空间,只是记录数据所需空间的大小。数据段存储经过初始化的全局和静态变量。


#define DEBUG "debug"  
  
int space[1024][1024];  
int data = 1;  
int no_data = 0;  
  
int main()  
{  
  char *a = DEBUG;  
  return 1;  
}  
  使用nm查看后
0000000000600660 d _DYNAMIC  
00000000006007f8 d _GLOBAL_OFFSET_TABLE_  
0000000000400578 R _IO_stdin_used  
                 w _Jv_RegisterClasses  
0000000000600640 d __CTOR_END__  
0000000000600638 d __CTOR_LIST__  
0000000000600650 D __DTOR_END__  
0000000000600648 d __DTOR_LIST__  
0000000000400630 r __FRAME_END__  
0000000000600658 d __JCR_END__  
0000000000600658 d __JCR_LIST__  
0000000000600820 A __bss_start  
0000000000600818 D __data_start  
0000000000400530 t __do_global_ctors_aux  
00000000004003e0 t __do_global_dtors_aux  
0000000000400580 R __dso_handle  
                 w __gmon_start__  
0000000000600634 d __init_array_end  
0000000000600634 d __init_array_start  
0000000000400490 T __libc_csu_fini  
00000000004004a0 T __libc_csu_init  
                 U __libc_start_main@@GLIBC_2.2.5  
0000000000600820 A _edata  
0000000000a00840 A _end  
0000000000400568 T _fini  
0000000000400358 T _init  
0000000000400390 T _start  
00000000004003bc t call_gmon_start  
0000000000600820 b completed.6347  
000000000060081c D data  
0000000000600818 W data_start  
0000000000600828 b dtor_idx.6349  
0000000000400450 t frame_dummy  
0000000000400474 T main  
0000000000600830 B no_data  
0000000000600840 B space
  可以看到变量data被分配在data段,而被初始化为0的no_data被分配在了BSS段。
  .bss是不占用.exe文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化。

  注意:.data和.bss在加载时合并到一个Segment(Data Segment)中,这个Segment是可读可写的。

代码段:

  又称为文本段。存储可执行文件的指令;也有可能包含一些只读的常数变量,例如字符串常量等。

  .rodata段:存放只读数据,比如printf语句中的格式字符串和开关语句的跳转表。也就是你所说的常量区。例如,全局作用域中的 const int ival = 10,ival存放在.rodata段;再如,函数局部作用域中的printf("Hello world %d\n", c);语句中的格式字符串"Hello world %d\n",也存放在.rodata段。

  但是注意并不是所有的常量都是放在常量数据段的,其特殊情况如下:
  1)有些立即数与指令编译在一起直接放在代码段。如下面int a=10中“10”为立即数;

int main()  
{  
  int a = 10;  
  return 1;  
}  

  a是常量,但是它没有被放入常量区,而是在指令中直接通过立即数赋值
  2)对于字符串常量,编译器会去掉重复的常量,让程序的每个字符串常量只有一份。

char *str = "123456789";  
char *str1 = "helloworld";  
  
int main()  
{  
  char* a = "helloworld";  
  char b[10] = "helloworld";  
  return 1;  
}  
  汇编代码如下:


                .file   "hello.c"  
.globl str  
        .section        .rodata  
.LC0:  
        .string "123456789"  
        .data  
        .align 8  
        .type   str, @object  
        .size   str, 8  
str:  
        .quad   .LC0  
.globl str1  
        .section        .rodata  
.LC1:  
        .string "helloworld"  
        .data  
        .align 8  
        .type   str1, @object  
        .size   str1, 8  
str1:  
        .quad   .LC1  
        .text  
.globl main  
        .type   main, @function  
main:  
.LFB0:  
        .cfi_startproc  
        pushq   %rbp  
        .cfi_def_cfa_offset 16  
        .cfi_offset 6, -16  
        movq    %rsp, %rbp  
        .cfi_def_cfa_register 6  
        movq    $.LC1, -8(%rbp)  
        movl    $1819043176, -32(%rbp)  
        movl    $1919907695, -28(%rbp)  
        movw    $25708, -24(%rbp)  
        movl    $1, %eax  
        leave  
        .cfi_def_cfa 7, 8  
        ret  
        .cfi_endproc  
.LFE0:  
        .size   main, .-main  
        .ident  "GCC: (GNU) 4.4.6 20110731 (Red Hat 4.4.6-3)"  
        .section        .note.GNU-stack,"",@progbits  
  可以看到str1和a同时指向.rodata段中同一个LC1
  3)用数组初始化的字符串常量是没有放入常量区的。

  4)用const修饰的全局变量是放入常量区的,但是使用const修饰的局部变量只是设置为只读起到防止修改的效果,没有放入常量区。
  5)有些系统中rodata段是多个进程共享的,目的是为了提高空间的利用率。
  注意:程序加载运行时,.rodata段和.text段通常合并到一个Segment(Text Segment)中,操作系统将这个Segment的页面只读保护起来,防止意外的改写。

堆:

  就像堆栈段能够根据需要自动增长一样,数据段也有一个对象,用于完成这项工作,这就是堆。堆区域是用来动态分配的内存空间,用 malloc 函数申请的,用free函数释放。calloc、realloc和malloc类似:前者返回指针的之前把分配好的内存内容都清空为零;后者改变一个指针所指向的内存块的大小,可以扩大和缩小,它经常把内存拷贝到别的地方然后将新地址返回。


栈、堆辨析:

1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。 
2、堆区(heap):由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 

程序示例:

1、举个例子说明各种变量存放在什么区: 

#include <stdlib.h>

int a=123; //a在全局已初始化数据区 

char *p1; //p1在BSS区(未初始化全局变量) 

int main()
{
    int b; //b为局部变量,在栈区 

    char s[]="abc"; //s为局部数组变量,在栈区 
            //"abc"为字符串常量,存储在已初始化数据区 

    char *p1,*p2; //p1,p2为局部变量,在栈区 

    char *p3="123456"; //p3在栈区,"123456"在常量区(.rodata)

    static int c=456; //c为局部(静态)数据,在已初始化数据区 

    //静态局部变量会自动初始化(因为BSS区自动用0或NULL初始化)

    p1=(char*)malloc(10); //分配得来的10个字节的区域在堆区 

    p2=(char*)malloc(20); //分配得来的20个字节的区域在堆区 

    free(p1);

    free(p2);

    p1=NULL; //显示地将p1置为NULL,避免以后错误地使用p1

    p2=NULL;
}

2、我们再写一个程序,输出各变量的内存空间:
#include <stdio.h>
#include <stdlib.h>

extern void afunc(void);

extern etext,edata,end;

int bss_var;//未初始化全局变量存储在BSS段

int data_var=42;//初始化全局存储在数据段

#define SHW_ADR(ID,I) printf("The %s is at address: %8x\n",ID,&I);//打印地址宏

int main(int argc,char *argv[])
{
    char *p,*b,*nb;

    printf("etext address: %8x\tedata address: %8x\tend address: %8x\t\n",&etext,&edata,&end);

    SHW_ADR("main",main);//查看代码段main函数位置

    SHW_ADR("afunc",afunc);//查看代码段afunc函数位置

    printf("\nbss Locatoin:\n");
    SHW_ADR("bss_var",bss_var);//查看BSS段变量地址

    printf("\ndata Location:\n");
    SHW_ADR("data_var",data_var);//查看数据段变量地址

    printf("\nStack Loation:\n");
    afunc();
    printf("\n");

    p=(char*)alloca(32);//从栈中分配空间

    if(p!=NULL)
    {
        SHW_ADR("string p in stack start",*p);
        SHW_ADR("string p in stack end",*(p+32*sizeof(char)));
    }

    b=(char*)malloc(32*sizeof(char));//从堆中分配空间
    nb=(char*)malloc(16*sizeof(char));//从堆中分配空间

    printf("\nHeap Location:\n");
    SHW_ADR("allocated heap start",*b);//已分配的堆空间的起始地址
    SHW_ADR("allocated heap end",*(nb+16*sizeof(char)));//已分配的堆空间的结束地址

    printf("\np,b and nb in stack\n");
    SHW_ADR("p",p);//显示栈中数据p的地址
    SHW_ADR("b",b);//显示栈中数据b的地址
    SHW_ADR("nb",nb);//显示栈中数据nb的地址

    free(b);//释放申请的空间,以避免内存泄露
    free(nb);
}

void afunc(void)
{
    static int level=0;//初始化为0的静态数据存储在BSS段中

    int stack_var;//局部变量,存储在栈区

    if(++level==5)
        return;

    SHW_ADR("stack_var in stack section",stack_var);
    SHW_ADR("leval in bss section",level);

    afunc();
}

/* Output
etext address:  80488bf    edata address:  8049b48    end address:  8049b58    
The main is at address:  80485be
The afunc is at address:  8048550

bss Locatoin:
The bss_var is at address:  8049b54

data Location:
The data_var is at address:  8049b40

Stack Loation:
The stack_var in stack section is at address: ff9cdf80
The level in bss section is at address:  8049b50
The stack_var in stack section is at address: ff9cdf50
The level in bss section is at address:  8049b50
The stack_var in stack section is at address: ff9cdf20
The level in bss section is at address:  8049b50
The stack_var in stack section is at address: ff9cdef0
The level in bss section is at address:  8049b50

The string p in stack start is at address: ff9cdf70
The string p in stack end is at address: ff9cdf90

Heap Location:
The allocated heap start is at address:  9020078
The allocated heap end is at address:  90200c8

p,b and nb in stack
The p is at address: ff9cdfac
The b is at address: ff9cdfa8
The nb is at address: ff9cdfa4
*/

内存管理函数:
这里插入一段对void*的解释:
void*这不叫空指针,这叫无确切类型指针.这个指针指向一块内存,却没有告诉程序该用何种方式来解释这片内存.所以这种类型的指针不能直接进行取内容的操作.必须先转成别的类型的指针才可以把内容解释出来.

还有'\0',这也不是空指针所指的内容.'\0'是表示一个字符串的结尾而已,并不是NULL的意思.

真正的空指针是说,这个指针没有指向一块有意义的内存,比如说:
char* k;
这里这个k就叫空指针.我们并未让它指向任意内存.
又或者
char* k = NULL;
这里这个k也叫空指针,因为它指向NULL也就是0,注意是整数0,不是'\0'.

一个空指针我们也无法对它进行取内容操作.
空指针只有在真正指向了一块有意义的内存后,我们才能对它取内容.也就是说要这样
k = "hello world!";
这时k就不是空指针了.

void *malloc(size_t size)
(typedef unsigned int size_t;)
malloc在内存的动态存储区中分配一个长度为size字节的连续空间,其参数是无符号整型,返回一个指向所分配的连续空间的起始地址的指针。分配空间不成功(如内存不足)时返回一个NULL指针。

void free(void *ptr)
free释放掉内存空间。

void *realloc(void *ptr,size_tsize)
当需要扩大一块内存空间时,realloc试图直接从堆的当前内存段后面获得更多的内在空间,并返回原指针;如果空间不够就使用第一个能够满足这个要求的内存块,并将当前数据复制到新位置,释放原来的数据块;如果申请空间失败,返回NULL。

void *calloc(size_t nmemb, size_t size)
calloc是malloc的简单包装,它把动态分配的内存空间进行初始化,全部清0。此函数的实现描述:
void *calloc(size_t nmemb, size_t size)
{
    void *p;

    size_t total;
    total=nmemb*size;

    p=malloc(total);
    if(p!=NULL)//申请空间
        memset(p,'\0',total);//初始化\0

    return p;
}

void *alloca(size_t size);
alloca在栈中分配size个内存空间(函数返回时自动释放掉空间,无需程序员手动释放),并将空间初始化为0。

--------------------- 
 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值