作者:冯老师,华清远见嵌入式学院讲师。

程序由指令和数据组成,C语言程序亦是如此。开发者在编写程序的时候往往需要根据不同数据的特点以及程序需求来选择不同的数据存储方式,那么在C语言中数据的存储分为哪些方式呢?

C程序大致来讲可以分为四个数据区:常量区,静态去,堆区,栈区。

其中常量区存储了未被作为初始化使用的字符串常量和被const修饰的全局变量,其特点是只可被访问不可被写入,生命周期同程序的运行过程。

静态区存储了全部的全局变量,和所有被static修饰的变量(包括全局和局部),其特点是生命周期很长(为一次程序的运行过程)并且只被初始化一次(在编译之后就已完成)。

栈区存储了所有自动存储(不加任何存储类型关键字修饰或被auto修饰)的局部变量,其特点是生命周期很短,仅仅是该变量所在函数的一次调用过程。运行时有操作系统分配并在函数结束后回收。

堆区是由操作系统负责维护的大片内存池,使用时需手动申请(调用malloc家族函数),但使用完毕后需手动释放,否则会造成严重的内存泄漏,直到该进程退出后才会被操作系统回收。

下面详细介绍每种存储类型的特点。

一.常量区:

故名思意,常量区里存放的是一些不可改变的量,比如字符串常量。在实际的ELF(Executable and Linkable Format,可执行连接格式,是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。Linux 作为类unix系统其程序仍然沿用了该格式。)的程序数据是分段存储的,对应的常量区就是“.rodata(只读数据)段“。

常量区的数据被标记为只读,也就是程序只有访问权而没有写入权,因此如果开发者需要使用某些不希望被改变的数据时可以将其放入常量区。

在C语言中常量有很多种,比如常见的:

字符常量:‘a’, ’A’, ’*’。

字符串常量:”helloworld”,”ilovechina”,”12345”。

整常量: 25,10,012,0x0a,0b00001010。

浮点常量: 3.14,123.456, 3.0E-23;

但是并不是所有的常量都会被编译器放在常量区的,如图1-1中代码所示:

图1-1 定义一个变量并被常量初始化

图中程序定义了一个整型局部变量i,并且被初始化为10,其中i是变量,10是常量,但是编译器并不将10放入常量区,而是在指令中直接通过立即数赋值(图1-2)。

图1-2 由图1-1程序编译生成的汇编代码

这是因为编译器认为普通的整型、浮点型或字符型常量在使用的时候是可以通过立即数来实现的,没有必要额外存储到数据区,如此节省了存储空间和运行时的访问时间。那么什么样的数据才将放入常量区呢?

1.字符串常量

如图1-1所示,在C程序中定义了一个局部的字符指针变量p指向一个字符串常量,其中p由于是局部变量被放在栈区,而字符串常量“helloworld”在汇编中被放入.rodata段(图1-2),在编译后生成的ELF格式文件中也将被放入.rodata段(图1-3)

图1-3 C语言的示例程序定义指针变量指向字符串常量。

图1-4 由图1-3中代码生成的汇编程序。

图1-5 由图1-3中程序编译生成的可执行程序分析。

但是,当一个字符常量串被用来为数组初始化的时候,那么该字符串常量将不会放入常量区,而是放入对应的数组中,如图1-6所示:

图1-6 定义一个字符数组并用字符串常量初始化

编译器会将该字符串按照四字节为一组转换成对应的32位整数来为该数组进行初始化,如图1-7所示,其中第13行的10进制整数1819043176转换成16进制为0x6c6c6568,正好是字符‘l’,’l’,’e’,’h’:

图1-7 图1-6中C代码生成的汇编程序

因此在编译生成的ELF格式文件中的.rodata段也将不会存储该字符串常量:

图1-8 图1-6程序编译后生成的可执行程序片段。

2.被const修饰的全局变量

a)

除了字符串之外,其他常量也可以放在常量区,但是前提是该数据必须被存放在全局变量的空间里,并且被const关键字修饰。如图1-9代 码所示:

图1-9 第4行定义了一个被const修饰的全局变量

编译生成的汇编程序比较:

其中value0由于被const修饰所以放在了.rodata段也就是所谓的常量区,而value1是一个普通的全局变量所以放在了.data段也就是所谓的静态数据区。分析编译生成的ELF格式可执行程序如下:

图1-11 value0的存放位置

其中value0的数据被放在常量区(.rodata段)十六进制显示的0a对应了它的十进制初始值10。

图1-12 value1的存放位置

Value1的数据被放在静态区(.data段)十六进制显示的14对应了它的十进制初始值20。

b)

但是并不是所有被const修饰过的变量都放在常量区,事实上只有全局变量才是如此,普通的局部变量被const修饰后仅仅意味着在表达式上不能显式地改变该变量值,否则编译器会报语法错误,但该变量仍存放在栈区。而由于其存储区域没有发生本质的改变,因此仍然可以通过其他方式改变其值,比如指针。如图1-13所示:

图1-13 定义两个局部变量,其中一个被const修饰

保存,编译,结果如下:

图1-14 编译器发生编译错误

由于 value1被const修饰,所以程序第9行的赋值语句将发生错误。

接着修改程序,通过指针去修改value1的值:

图1-15 定义指针p指向value1并通过指针赋值。

编译,运行:

图1-16 编译运行结果

由于定义的指针变量p和表达式&value1的类型不匹配(p是int *,而&value1的类型是const int *)所以在第7行赋值的时候编译器会产生一个类型不匹配的警告,我们忽略该警告继续运行,结果改变了value1的值。

3.由常量区引发的段错误

由于常量区的特性是只读,因此当程序试图去向指向常量区的地址写入数据的时候,操作系统处于安全考虑会发出一个段错误的信号并且杀死该进程,以达到保护操作系统的目的。

图1-17 示例代码,通过指针去向常量区写数据。

第10行和11行的均可产生同样的段错误,如图1-18所示

图1-18 非法写入引发的段错误

浅谈C语言的数据存储(一)的更多相关文章

  1. 浅谈C语言的数据存储(二)

    作者:冯老师,华清远见嵌入式学院讲师. 静态区是一个抽象笼统的概念,在实际的Linux/C的可执行程序中并没有静态区这个区域,具体来讲它主要由两个段组成:.data段和.bss段.其中.data段就是 ...

  2. 浅谈C语言中的强符号、弱符号、强引用和弱引用

    摘自http://www.jb51.net/article/56924.htm 浅谈C语言中的强符号.弱符号.强引用和弱引用 投稿:hebedich 字体:[增加 减小] 类型:转载 时间:2014- ...

  3. 浅谈Java语言环境搭建-JDK8

    title: 浅谈Java语言环境搭建-JDK8 blog: CSDN data: Java学习路线及视频 1.What's the JDK,JRE JDK(Java Development Kit ...

  4. 浅谈c语言结构体

    对于很多非计算机专业来说,c语言课程基本上指针都不怎么讲,更别说后面的结构体了.这造成很多学生对结构体的不熟悉.这里我就浅谈一下我对结构体的认识. 结构体,就是我们自己定义出一种新的类型,定义好之后, ...

  5. 浅谈C语言中断处理机制

    一.中断机制 1.实现中断响应和中断返回 当CPU收到中断请求后,能根据具体情况决定是否响应中断,如果CPU没有更急.更重要的工作,则在执行完当前指令后响应这一中断请求.CPU中断响应过程如下:首先, ...

  6. 浅谈Oracle中物理结构(数据文件等。。。)与逻辑结构(表空间等。。。。。)

    初始Oracle时很难理解其中的物理结构和逻辑结构,不明白内存中和硬盘中文件的区别和联系,我也是初学Oracle,这里就简单的谈谈我我看法. 首先,你需要明白的一点是:数据库的物理结构是由数据库的操作 ...

  7. 浅谈c语言代码段 数据段 bss段

    代码段.数据段.bss段 (1)编译器在编译程序的时候,将程序中的所有的元素分成了一些组成部分,各部分构成一个段,所以说段是可执行程序的组成部分. (2)代码段:代码段就是程序中的可执行部分,直观理解 ...

  8. 浅谈c语言的指针

    对于非计算机专业的同学,c语言的指针往往就是老师的一句“指针不考“就带过了.c语言的指针号称是c语言的灵魂,是c语言中最精妙的部分. 指针本质上也是变量,也就是一段内存,只是他的特殊之处是他存储的数据 ...

  9. 浅谈c语言中的堆

    操作系统堆管理器管理: 堆管理器是操作系统的一个模块,堆管理内存分配灵活,按需分配. 大块内存: 堆内存管理者总量很大的操作系统内存块,各进程可以按需申请使用,使用完释放. 程序手动申请&释放 ...

随机推荐

  1. 详解js的bind、call、apply

    详解js的bind.call.apply 说明 虽然bind.call.apply都是js很基础的一块知识,但是我从未认真总结过这三者的区别. 由于公司后端是用的微服务架构,又没有中间层对接,导致前端 ...

  2. Spring 配置内容外部化

  3. Linux gcc版本升级

    Linux gcc版本升级 操作环境 RHEL7,在进行内核编译时编译器版本,需要升级gcc编译器,选择gcc-8.3.0. http://ftp.gnu.org/gnu/gcc 里面提供所有的gcc ...

  4. 你确定你了解什么是linux系统?

    1.什么是linux发行版 就Linux的本质来说,它只是操作系统的核心,负责控制硬件.管理文件系统.程序进程等,并不给用户提供各种工具和应用软件.所谓工欲善其事,被必先利其器,一套在优秀的操作系统核 ...

  5. Python 超级玛丽代码实现:人物行走和碰撞检测

    功能介绍 人物行走 人物的行走速度这边分成水平方向(X轴)和竖直方向(Y轴),水平方向的速度要考虑加速度和摩擦力,竖直方向的速度要考虑重力加速度. 水平方向:设定X轴向右走的速度为大于0,向左走的速度 ...

  6. Python 线性回归(Linear Regression) 基本理解

    背景 学习 Linear Regression in Python – Real Python,对线性回归理论上的理解做个回顾,文章是前天读完,今天凭着记忆和理解写一遍,再回温更正. 线性回归(Lin ...

  7. spring get方法 中文(UTF-8)乱码

    问题: 前端用Get方法进行如下请求: 在浏览器中输入:http://localhost:8080/dmaList/ExportBySQL?sql=&names=分区级别&size=1 ...

  8. Java Collection集合中的iterator方法

    Iterator接口的概述 /** * java.util.Iterator接口:选代器(对集合进行遍历) * 有两个常用的方法 * boolean hasNext() * 如果仍有元素可以迭代,则返 ...

  9. 牛客网上的ST阶跃表

    给你一个长为n的序列a和一个常数k 有m次询问,每次查询一个区间[l,r]内所有数最少分成多少个连续段,使得每段的和都 <= k 如果这一次查询无解,输出"Chtholly" ...

  10. 19徐州网络赛E 线段树加离散化

    题目链接:https://nanti.jisuanke.com/t/41387 按wi的值建立权值线段树维护值为wi出现的最后位置,对于第i个人的答案,查询线段树[wi+m,max]区间的最大位置po ...