首页 > 代码库 > 内存分配的四个例子

内存分配的四个例子

内存分配的四个例子

原文在是:有关内存的思考题 在这篇基础上扩展了些知识,以做记录。


第一个例子:

char *GetMemory(char * p) {
    p = (char *)malloc(100);
    return p;
}

void Test(void) {
    char *str = NULL; 
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}
请问运行Test函数会有什么样的结果?
答:程序崩溃。
这是函数形参的问题,函数GetMemory的输入参数char * p是一个形式参数。
形式参数在函数定义时并不占用内存中的存储单元;
形式参数在函数调用时被分配内存单元(在栈上),并且其初始值与被赋的实参相同,调用以后形参与实参之间没有任何联系,它们是物理地址不同的两个变量。因此在函数内部对于形参所做的改变,是不能在实参上得到体现的;
形式参数在函数结束时被释放,它的值将被丢弃。

Test函数中的 str一直都是 NULL。strcpy(str, "hello world");将使程序崩溃。
函数改成:
void Test(void) {
    char *str = NULL; 
    str = GetMemory(str);
    strcpy(str, "hello world");
    printf(str);
}
就对了,但是malloc的内存没有释放,有内存泄露问题。


第二个例子:
char *GetMemory(void) {
    char p[] = "hello world";
    return p;
}

void Test(void) {
    char *str = NULL;
    str = GetMemory();    
    printf(str);
}
请问运行Test函数会有什么样的结果?
答:可能是乱码。
这是个内存分配问题,char p[] = "hello world";这句话可以理解为:a.在栈上分配长度为12的内存,p指向这个内存地址;b.把"hello world"拷贝到p指向的栈内存里面,再加上一个结束字符‘\0‘(这也是为什么11个字符,长度为12的原因)。p和p指向的地址都在栈上面。当函数退出的时候,释放使用的栈空间给其他的函数使用,因此该地址上的值有可能被其他函数重写,这样打印出来的可能是乱码。由于栈释放只是把栈顶指针移位,并没有把释放的数据段初始化,因此释放后的数据还有保留,如果又没有其他函数重写这部分栈空间,那么打印出来的数据也有可能是对的。

函数改为:
char *GetMemory(void) {
    char *p = "hello world";
    return p;
}
其他不变,这个Test函数运行的结果恒定为"hello world"。
这是因为:char *p = "hello world"; p是在栈上分配的,"hello world"定义在文字常量区。一个栈上的变量p指向文字常量区的一个字符串。函数返回后str指向的也是这个文字常量区,因此打印的结果也是对的,而且文字常量区是由编译器自动创建释放,不存在内存泄露问题。
类似引申一个问题
    char *p0 = "hello world";
    char p1[] = "hello world";
    *p0='W';   // 可以修改,因为p0指向的空间在栈上 
    *p1='W';   // 系统错误,因为试图修改文字常量区内容。
这就是这个问题的本质区别。


第三个例子:

void GetMemory(char **p, int num) {
    *p = (char *)malloc(num);
}

void Test(void) {
    char *str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello world");   
    printf(str);    
}
请问运行Test函数会有什么样的结果?
答:
(1)能够输出hello world
(2)存在内存泄漏
GetMemory输入实参是char *str的地址(指针的地址),在函数里面改变了这个地址指向的空间,因此返回后可以带出修改后的数据值。但是str空间没有被释放,所以有内存泄露。如果不是字符串,这个问题似乎更容易理解。
int get_x(int x, int *y) {
   x++;
   *y = x+1;
   return x;
}

void Test(void) {
   int x = 100;
   int y = 200;
   int z = get_x(x, &y);
   printf("x=%d y=%d z=%d\n", z, y,z);
}
输出结果为:x=100 y=102 z=101。

第四个例子:
void Test(void) {
    char *str = (char *) malloc(100);
    strcpy(str, “hello”);
    free(str);      
    if(str != NULL) {
      strcpy(str, “world”);
      printf(str);
    }
}
请问运行Test函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料。
因为调用free(str)之后,str成为悬垂指针,它还指向原来的堆地址空间(因此str!=NULL 返回为1),但是那个空间已经被系统回收,它已经不是一个系统可知长度的独立的被标识为已用的内存空间段。如果这时候还强行向其中写数据,也不一定会出错。因为该地址真实存在,但是这是不安全的,1.可能写到下一个数据空间,写花别人;2.可能系统把包含这个数据段的空间重新分配给其他指针,然后那个指针赋值后,就写花了自己。这两种情况都是不可预知的,因此要尽量避免这种情况出现。避免的方式很简单 free(str); str=NULL;这两句话不要分开。

内存分配的四个例子