0%

程序和内存 01.初步认识内存

程序和内存 01.初步认识内存

接下来我们一起来探索一下在写程序的时候如何使用内存空间的!

要求掌握某种编程语言的基础, 有C语言基础更好。

程序世界里的内存

我们的操作系统的内存管理单元把物理的内存这样一个能看得见摸得着的硬件抽象出来形成了一个几乎能让应用程序无线使用的存储空间了。

我们讨论的主题是操作系统帮助我们抽象出来的上层应用程序能够自由使用的内存空间, 而不是物理内存, 如果你对操作系统的内存管理单元感兴趣可以参考操作系统原理。

一般情况下每一个应用程序有自己独立的内存空间, 两个相邻的应用程序互不干扰, 都是独立存在的。

那么这里有人可能会问, 有些程序能读写其他程序的内存, 那么为什么会这样? 比如我们的屏幕阅读程序, 还有其他一些非法程序。

简单回答, 因为这是二班情况, 不在我们今天讨论的范围内。

正经答案, 因为操作系统虽然隔离了每个应用程序的内存空间, 但是为了调试; 为了增强系统的功能还是提供了一系列的接口, 通过这些功能某个应用程序可以读写其他任何一个应用程序的内存空间。

一个程序的内存空间至少有如下几个组成部分, 就像你们家房子一样, 有客厅卧室厨房和卫生间一样, 为了方便程序内存空间也划分了不同的区域。

  • 站区 存放函数的局部变量 轻量级数据为主

  • 堆区 存放动态数据, 重量级数据为主

  • 静态区 存放字面量; 全局变量; 静态变量; 只读变量

    暂时不考虑内存空间的其他区域, 我们这次主题主要要探索站区; 堆区和静态常量区。

    重中之重是堆区!

堆区和静态常量区的区别

通常认为字符串是重量级数据, 所以放在内存的堆区, 但是字符串字面量却放在了静态常量区域。

堆区里的字符串可以随意修改, 而静态常量区的字符串却受保护, 只能读取, 不可修改。

char *str1 = "hello"; // 静态常量区
char str2[] = "hello"; // 堆区

接下来编写一个函数来验证我们的猜测, 建议使用Linux最好的发行版Windows10。

为了避免文章过于冗长省略了完整程序。

// 原地修改字符串为大写, 需要ctype.h和string.h两个头文件
void toUpperCase(char *s)
{
size_t len = strlen(s);
for (int i = 0; i < len; i++)
{
if (islower(s[i]))
{
s[i] -= 32;
}
}
}

然后可以在main函数里测试了

char *s = "hello world";
toUpperCase(s);
println("%s\n", s);

打开bash编译没有出现错误和警告, 运行的时候出现了问题。

$ cc upper.c
$ ./a.out
Segmentation fault (core dumped)

那么这个Segmentation fault (core dumped)是个什么玩意儿?

程序段故障, 很显然我们修改了不该修改的内存。

改为

char s[] = "hello world";

后编译运行没有任何异常。

所以最后的结论字符串数组在堆区, 可以任意修改, 而字符串常量在静态常量区不可随意修改。

char *s = "hello";

这里的*s指针指向了静态常量区的某个空间,

总结

  • 应用程序的内存空间分为了若干部分, 这样可以方便的管理内存空间

  • 这些内存区域至少包括站区; 堆区和静态常量区

  • 每个区域的使用场景各不相同, 存储的数据量也不相同

  • 静态常量区的内存空间受保护不可修改, 而堆区的内存则可以自由的随意修改

  • 字符串数组保存在堆区, 而指向字符串的字符指针不一定指向了哪里, 也许是静态常量区, 也许是堆区, 或者指向了某个站区上的局部变量, 当然这个局部变量里保存了一个字符

    下一次我们看看站区和堆区的区别。

    未完待续……