c语言报错 malloc corruption_c语言入门 第二十六章 存储类型

我们在声明一个变量的时候,需要声明变量的类型,变量的类型决定了变量占用的空间大小和存储形式

有的时候我们除了需要确定变量的类型之外,还需要确定变量在内存当中的存储位置,这里就需要用到另外一些关键字,我们把他们统称为存储类型或者存储类别

在了解存储列别之前,我们首先需要知道c语言程序在内存当中是如何分布的,我们把这个叫做内存模型

在程序执行的过程当中,我们会把程序占用的内存划分为这样几个区域

74b7823f4d41b4827265a46e906bc26f.png
  • 代码区:用来存放编译之后的函数代码,也就是机器码,每个函数的代码只需要保存一份,调用一次就执行一次
  • 常量区:一般用来保存使用""来表示的字符串常量,其他的数据常量和用#define定义的宏常量是保存在代码区里面的
  • 全局数据区:主要用来保存在程序当中出现的全局变量和静态变量,静态变量就是使用static关键字修饰的变量,我们一会再仔细讲
  • 栈区:就是程序执行过程当中,由系统控制自动分配和释放的空间,这里主要保存的是函数的参数,返回值和局部变量,这部分的空间会随着程序的执行自动的加载和释放
  • 堆区:由程序员手动控制分配和释放的空间,我们使用malloc和free分配和释放的就是这部分空间

不同储存空间当中储存的数据(函数也可以认为是一种数据),它的作用范围和生命周期是不一样的

之前我们了解过在函数之外声明的变量是全局变量,在函数之内声明的变量是局部变量

#include

之前我们在解释变量的作用域的时候是通过代码块的划分来说明的

a 定义在c文件当中,所以在整个文件当中都可以使用, b 定义在函数 fun 里,所以只有 fun 函数内部可以使用

其实使用内存布局一样可以解释这个过程

  • 首先把程序编译之后的代码加载到代码区
  • 然后建立全局存储区,这个时候变量 a 就存在了
  • 调用主函数,把主函数的代码放到栈区里面来执行,在这个过程当中 a 已经存在了,所以使用 a 是没问题的
  • 当主函数执行到调用 fun 函数的时候,把 fun 函数再放到栈区当中执行,建立变量 b ,当函数 fun 执行完毕之后 变量 b 被自动的销毁,所以变量 b 只存在于 fun 被调用执行的过程当中
  • 主函数执行结束被销毁,程序结束

在全局区当中声明的变量,如果没有赋值,系统会自动的把他初始化为0,而在栈区当中出现的变量,就不会被初始化为0,而是会表现出一个随机的值

6d0144f0af235c2f15b59b3856b26e04.png

我们使用malloc来手动分配的空间一般不会被释放掉,因为是保存在堆区当中的

#include

这里的局部变量 b 是在程序执行过程当中在栈区创建的变量,在函数结束之后已经被销毁了,所以在主函数当中的到的地址是一个已经被销毁的区域,程序在执行过程当中会出问题

#include

这里的变量 b 是由malloc函数创建的,保存在堆区, fun 函数执行之后数据不会自动销毁,在函数执行之后返回的地址依然存在,所以可以输出函数创建的数据

fe772f1cdac890aa760f990ff96a931b.png

数据声明的位置一般就决定了数据的存储类型,当然我们也可以通过加关键字来改变一个变量的存储类别

这里一般会用到4个关键字:auto(自动的)、static(静态的)、register(寄存器的)、extern(外部的)

auto这个关键字只能用在局部变量前面,同时也是变量的默认类型,所以使用auto和不使用auto的结果是一样的,我们一般都不用在每个变量前面加上 auto

register 关键字表示在条件允许的情况下把变量保存在CPU的寄存器当中而不是内存当中,这样可以提高数据的读写速度,但是使用 register 修饰的变量不能使用取地址符&取到它的地址

#include

0bacb90f45460a9307ef0a235509a4c8.png

输出register变量的地址程序会出错

static 可以放在局部变量前面也可以放在全局变量前面

当static变量放到局部变量之前的时候,表示这个局部变量是一个静态变量,被保存在静态存储区

#include

在函数 fun 中声明了一个静态变量 a ,第一次调用 fun 的时候 a 被声明并且保存到静态存储区当中,同时初始化为0,当函数执行结束后,a的值变为1,第二次调用的时候 a 已经在静态存储区里面了,所以不需要再次初始化,同时再次自增,a 的值变为2

2b0c49a0ec8dd7e31c2166f3a4621d92.png

extern 表示外部的,这里需要理解两个概念:内部链接和外部链接

简单来说,我们写的每一个c文件在编译之后就可以认为是一个独立的编译单元

假如我们的c程序不是由一个c文件而是由多个c文件构成,那么每个c文件都需要独立的编译,这样就会产生多个编译单元,假如 a 文件中声明的函数或者全局变量,b文件当中也会用到,那么就需要把a和b链接到一起,完成这个工作的工具叫做链接器

一般的编译器都包含了链接器,链接的过程也会自动的完成,我们需要做的就是告诉链接器,每个编译单元的哪些内容是只有这个编译单元自己可以使用的,哪些内容是可以由其他编译单元共享使用的

只能在自己的编译单元当中使用的内容就是内部链接,允许其他编译单元使用的内容就是外部链接

extern和static都是用来指定存储类别是静态的

  • 但是使用static声明的内容被认为是内部链接只能在自己的编译单元当中使用;
  • 而extern声明的内容被认为是外部链接,允许其他的编译单元使用

对于全局的内容来说默认的存储类别就是extern

但是如果两个不同编译单元当中,同一个全局变量或者函数没有extern的声明,那么就会被认为是两个不同的内容,如果使用了extern声明,就会被认为是同一个内容

我们为了看的更清楚一些不使用IDE而使用命令行来完成这个过程

在同一个文件夹下面建立两个文件 a.c 和 b.c

a.c

#include

b.c

void 

a文件当中定义了一个函数fun

b文件当中只有一个函数声明fun

那么函数可以认为是默认的外部链接,我们同时编译这两个文件,在控制台上输入命令

gcc a.c b.c

会自动生成一个 a.out 文件,windows下面会生成一个a.exe文件,执行这个文件

./a.out

window可以

a.exe

53bc88a9cfc601cbeaa82b5eded2dfe8.png

这里的原理就是 a在编译的时候表示我这里有一个函数 fun 的定义,而在 b 编译的时候表示我这里有一个 fun 函数的声明,那么链接器就自动的把 a 中fun的定义和 b 中fun的声明链接到了一起,认为他们就是同一个函数

假如我们把 fun 改为static

a.c

#include

再同时编译两个文件就会报错

5606d34203a325396ad869ec53ea9c73.png

说明static限定了函数是内部链接,其他的编译单元不能使用

对于变量也是一样

a.c

#include

b.c

int 

528344bb7fe75a4d1351f8abfc9e771a.png

但是变量不容易区分定义和赋值,所以我们可以在声明变量的地方加上extern,来作为区分

extern 

最后总结一下

变量和函数的作用域可以认为分为3个级别 局部变量,全局内部链接和全局外部链接

对于局部变量保存在栈内存当中

对于全局变量保存在静态区(全局区)当中

使用static修饰的局部变量可以保存在静态区(全局区)当中

static修饰的全局成员,包括全局变量和函数可以在本编译单元当中使用

extern修饰的全局成员,包括全局变量和函数,可以通过链接在其他文件当中使用

全局变量和函数默认使用extern来修饰

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值