C Primer Plus 第 12 章 存储类别,链接和内存管理

关键字:auto, extern, static,const
函数:malloc(),free()
主要内容:变量的作用域(可见的范围)和生命周期(它存在多长时间)


存储类别总结

C语言中,有5种存储类别(不包括线程)。下面先定义,再慢慢解释具体的概念。

自动:

无特殊说明或者带auto说明符声明的变量,自动存储期,块作用域,无链接。如果未初始化,其值未定义。

静态,外部链接:

在所有函数外部并且 没有 使用 static 来声明变量是静态的,静态存储期,文件作用域,外部链接。只能被编译器初始化一次。如果未初始化,其值默认为0。外部链接的意思是,其他 .c 文件 可以 通过extern来调用该文件中定义的此变量。

静态,内部链接:

在所有函数外部并且 使用 static 来声明变量是静态的,静态存储期,文件作用域,内部链接。只能被编译器初始化一次。如果未初始化,其值默认为0。外部链接的意思是,其他 .c 文件 不可以 通过extern来调用该文件中定义的此变量。

静态,无链接:

在某块中被 static 说明符声明,静态存储期,块作用域,无链接。只能被编译器初始化一次。如果未初始化,其值默认为0。

寄存器变量:

未使用过,暂不介绍。


概念解释

存储期

存储期表示一个变量在内存中存在了多长时间。用作用域和链接去描述标识符(诸如static, const, extern)。

作用域和链接

作用域和链接可以表明,这个变量可以被程序的哪个块,哪个函数,哪个文件所调用。


概念详解

自动:

学习编程期间,我们见到的大多数变量都是自动变量。它在 块中(任意一对花括号中间)被定义,并且无static声明。自动变量只在被定义的块中有效。它在程序执行到块中声明语句时被创建, 它在函数进入块时被分配内存,在该块结束时被自动释放。

静态:

static变量恐怕是很多初学者的FAQ,其实了解后就会发现它的定义非常简单。那就是static的变量,在程序被载入到程序结束期间都存在,当程序结束时被自动释放(即使该static变量是在某块中被初始化,它占用的内存不会在块结束时被释放。)

外部,内部,无链接:

这三个概念其实也非常简单。当一个变量像我们include stdio.h 一样,定义在所有函数外部,那么这个变量就是有链接的。如果他同时被static声明,那么它是内部链接,内部链接不能被其他 .c 通过extern访问,我们可以理解成只在该文件内的 “全局变量”。相反,如果没有static声明,那么该文件可以被其他 .c文件通过extern访问,它是所有文件都能用的“全局变量”。而静态无链接变量,这种储存类型的文件只能在被定义的块中被访问,换句话说,此变量的作用域是定义它的块。


存储类别异同和注意事项

  • 自动变量在被初始化时,它的值会继承这块内存上一次储存的值(如果存在),总之你别期待着它自动变成 0 。但是如果我们定义 static 变量时,没有给它初始化,那么它的值默认0 。如果变量是数组名,那么所有数组成员都为0 。

  • 一般我们在创建自动变量时,不需要加上 auto,一般加auto是强调,我需要创建一个和 static 变量同名的变量名,来特指。个人建议没事闲的别建同名的变量。

  • 块作用域static变量举例,比如for 循环中的 static 声明,例如:

for (int i = 0; i < 2; i++) {


        int x = 1;

        static int y = 1;
        
        printf("x is %d, y is %d", ++x, ++y);
    }
可以设想一下,这个函数运行的时候printf都会输出什么,
答案是,x是自动变量,自动变量在块结束时都会被释放。
每次进入for循环块,x都会被重新声明为1,所以printf的第一项永远为2,
但是static int y = 1这条语句在程序开始时就被分配内存。

static int y = 1; 这条声明实际上不是for的一部分。
如果开启调试模式,你会发现,程序似乎   跳过  了这条声明。
因为静态变量和外部变量在程序被载入时内存已执行完毕。
把这条语句放在for里面是为了告诉编辑器,   只有这个for循环  才能  “看到”  该变量。
**这条声明未在运行时执行。**
  • 什么时候需要用到extern?

    • 如果一个变量定义在另一个文件内,而我们要在自己的文件内使用它,那么必须使用 extern 来声明,我使用了其他文件中的 静态外部链接 变量。

    • 声明了外部变量,可以在函数中使用extern来强调,我接下来要用的变量是外部变量。

int num;

int arr[10];

int main(void){

        extern int num;

        extern int arr[];

}

这里其实完全可以省略 extern int num; extern int arr[]; 这两条代码,
它们仅仅是为了强调这两个变量是全局变量。
但是要删除的话就全删除掉, 如果只删除了 extern,那么实际上你是建立了两个同名的自动变量。
  • 我们正常创建的函数,都是外部定义的,所以这些函数都可以被其他 .c中使用 extern调用。同时函数也可以被静态定义,静态函数只能在该文件内使用。

  • 外部变量只能初始化一次,且必须在定义变量时进行,并且初始化只能使用常量表达式。


malloc和free

malloc函数接收一个参数,那就是大小,返回一个参数,代表生成内存的首字节地址。把这个地址赋值给一个指针变量,指针就可以访问这个内存了。

malloc生成的内存是void类型的,所以在使用的时候尽量使用强制类型转换。

int *res = (int *)malloc(sizeof(int) * ASize); // (刷过leetcode懂得都懂。。。)

free函数的参数,是之前malloc返回的地址。既然是地址,我们就没必要非得free当时malloc赋值给的那个指针,只要你free参数的指针,指向的是那个地址就行。

老生常谈的问题,为什么一定要free。假设你在for循环中malloc并指向一个自动变量指针,如果没有在for循环中free,那么这个自动变量在每次块结束的时候就被释放,相当于你就再也找不到这个地址了,想释放都释放不了。那么循环1万次,每次都无法释放,让冗余的数据占据内存,之后很可能就堆栈溢出了。这类问题就叫内存泄漏(Memory Leak)。


存储类别和动态内存分配

这个部分详细说的话,可以再写一篇博客了。所以这里只说结论。

Malloc生成的变量,存放在堆。

自动变量,都存放在上。

堆和栈加起来,叫动态存储区,这个地方的内存一会变大一会变小,所以是动态的。

静态 、常量、全局变量,存储在静态存储区,也叫全局存储区。


堆和栈的五大区别:

(我在百度上一搜,反而是一个广告网站里总结的挺好,就不附上链接了怕广告吓到你。。。)

  1. 申请方式不同。栈由系统自动分配,堆是程序员认为申请开辟。

  2. 申请大小不容。栈获得的空间小,堆获得的空间大。

  3. 申请效率不同。 栈由系统自动分配,速度快,堆速度较慢。

  4. 存储内容不同。 栈在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数的各个参数进栈,静态变量不入栈。堆一般在头部用一个字节存放堆的大小,堆中内容人为安排。

  5. 底层不同。 栈是连续的空间,堆是不连续的空间。


const类型限定符使用

类型限定符一共有三种: const, volatile,restrict. 由于 const 最常用并且我还没有用到过其他两个限定符,就先专门总结一下const吧。

const 的目的是让被定义的变量无法被改变,听起来挺简单的,不过很多使用的时候也会有一些小坑。


const声明指针时候的位置


const int *p;

int * const p;

const int * const p;

最上面两条代码是有区别的。关注点在 const 是在 * 左边还是右边。

第一条代码声明了一个 int 型指针变量,p解引用(*p的值)是不能被改变的。但是,p可以指向其他的值。

第二条代码声明了一个固定地址的p,即该指针只能指向这个地址,但是这个地址上面的值是可以被改变的。

第三条代码当然是全部都固定住不能改变。

const in *p = NULL;

*p = 5;    // 这里会报错: Read-only variable is not assignable

虽然解引用(*p的值)是不能被改变的。但是,p可以指向其他的变量,解引用获得其他的值。

int main(void)

{   

    int a = 5;

    const int *p = NULL;

    p = &a;

    printf("%d", *p);

}

对全局数据使用const

外部链接的全局变量可能被其他文件 extern调用,所以为了防止他们被意外更改,还是加上const较好。

同时如果为了省事,我们可以把 const double PI = 3.14 这种语句写在头文件里面,然后在其他 .c 文件中 extern就好了。

但是这里也有坑,那就是写在 头文件 中的全局变量声明,最好加上 static ,因为如果不加,那么每个 include 了这个 头文件的 .c文件,都会多出一句这个代码 : const double PI = 3.14

而且这种声明都是外部链接啊,那到时候万一有的文件没有 include 这个 头文件,而是选择 extern 这个变量, 那么编译器怎么知道它到底想要的是哪个 PI?

其实C标准是不允许这么做的,所以写在头文件里面的东西,最好还是加上 static,要不然容易出问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352