C/C++程序占用的内存分为两大类:静态存储区与动态存储区。其示意图如下所示:
数据保存在静态存储区与动态存储区的区别就是:静态存储区在编译-链接阶段已经确定了,程序运行过程中不会变化,只有当程序退出的时候,静态存储区的内存才会被系统回收。动态存储区是在程序运行过程中动态分配的。
在其它地方我们还可以看到内存分配还有其他分类,那些都是细分的分类,比如文字常量区、全局数据区等,都归为静态存储区这一个大类。
先看一个return返回指向栈内存指针的例子:
#include
char *GetStr(void)
{
char ptr[] = "Hello"; /* 保存在栈中 */
return ptr;
}
int main(void)
{
char *str = NULL;
str = GetStr();
printf("%s\n", str);
return 0;
}
warning: function returns address of local variable
运行结果并不是我们期望的输出字符串Hello。
那是因为GetStr函数返回指向栈内存的指针,这里的变量ptr是局部变量,而局部变量是分配在栈上的。即Hello保存在栈内存上,栈内存在函数调用结束时会自动销毁,因此此时的ptr里的内容是未知的,所以结果无输出。
下面我们把GetStr函数修改为:
char *GetStr(void)
{
char *ptr = "Hello"; /* ptr在栈上,Hello在静态区(常量区) */
return ptr;
}
此时编译运行的结果是怎样的呢?结果为:
可以看到能正常输出。为什么这里又可以正常输出呢?因为这里的ptr虽然分配在栈上,但是此时的Hello是一个字符串常量,其存储在静态存储区。在调用GetStr函数结束时其也不会被销毁。
这里可能有些人会有疑惑,同样是Hello,为什么一个在栈上,一个在静态区。
char *ptr = “Hello”;
此处首先定义了一个指针变量ptr,编译器就会为指针变量开辟了栈空间。而此时并没有空间来存放Hello,所以Hello只能存储在静态区。
char ptr[] = “Hello”;
此处首先定义一个数组ptr,因为未给出数组大小,所以此时数组大小未确定。然后把Hello保存在这个数组里,编译器就会为数组ptr开闭足够的栈空间来存储Hello。
从上面的例子我们知道,若函数返回指向栈内存的指针,所得到的结果并不是我们想要的。除了上面的方法之外,这里还有如下几种解决方法:
1、把ptr定义为全局变量,因为全局变量存储在静态存储区,程序结束才会释放。但是这样会导致函数是不可重入的。关于函数的重入与不可重入可查看往期笔记:什么是可重入函数?
2、在GetStr函数中使用malloc申请动态内存,但使用完一定要记得使用free进行释放,否则会导致内存泄漏。示例代码如下:
#include
#include
#include
char GetStr(void)
{
char ptr = (char)malloc(64sizeof(char));
strcpy(ptr, “Hello”);
return ptr;
}
int main(void)
{
char str = NULL;
str = GetStr();
printf("%s\n", str);
free(str); / 释放str指向的堆内存 */
return 0;
}
3、可以将变量ptr声明为static静态变量。但这也会导致函数是不可重入的。示例代码如下:
char *GetStr(void)
{
static char ptr[] = “Hello”;
return ptr;
}
C语言中没有特定的字符串类型,常用以下两种方式定义字符串:一种是字符数组,另一种是指向字符串的指针。如下:
(1)char str[] = "happy";
(2)char *str = "happy";
这种方式有什么不同呢?
下面看两个例子:修改字符串中的字符
示例1:
#include
int main(void)
{
char str[20] = "hello";
str[0] = 'H';
printf("%s\n",str);
return 0;
}
运行结果:
Hello
示例2:
#include
int main(void)
{
char *str = "hello";
str[0] = 'H';
printf("%s\n",str);
return 0;
}
运行结果:
无打印信息输出
可见,使用(1)方式定义的字符串其字符是可以修改的,使用(2)方式定义的字符串其字符是不可以修改的。(2)中可以成功编译和链接,但运行时可能会出现错误,我编译与运行的平台是window10平台,运行结果是无打印信息输出,在其他不同的平台运行可能会出现段错误(Segment Fault)或者写入位置错误。
这两种表示字符串的方式的主要区别是:字符串指针指向的内容是不可修改的,字符数组是可以修改的,即(2)方式定义的字符串保存在常量区,是不可更改的,(1)方式定义的字符串保存在全局数据区或栈区,是可修改的。
内存的分配方式:
内存分配可分为三种:静态存储区、栈区、堆区。
1、静态存储区:该内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,它主要存放静态数据、全局数据和常量。
2、栈区:它的用途是完成函数的调用。在执行函数时,函数内局部变量及函数参数的存储单元在栈上创建,函数调用结束时这些存储单元自动被释放。
3、堆区:程序在运行时使用库函数为变量申请内存,在变量使用结束后再调用库函数释放内存。动态内存的生存期是由我们决定的,如果我们不释放内存,就会导致内存泄漏。