【转】C数据存储(包括const存储在哪,C++不同部分我在文中用红字已指出)

非原创(文中红字为自己见解,如有不对,请大神指点)

  程序由指令和数据组成,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修饰的全局变量

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

图1-10

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

图1-11 value0的存放位置

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

图1-12 value1的存放位置

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

b)

  但是并不是所有被const修饰过的变量都放在常量区,事实上只有全局变量才是如此,普通的局部变量被const修饰后仅仅意味着在表达式上不能显式地改变该变量值,否则编译器会报语法错误,但该变量仍存放在栈区。C++不同的地方就在此,C++鼓励使用const来取代#define,因为C++对const进行了优化,如果该变量的值是常量表达式,在C++中就会进行常量折叠(const folding),何为const folding,百度一大堆,简单来说就是在编译期间任何出现该变量的地方都会被替换成常量表达式的值,正因此,这种情况下const定义的变量是可以用于定义数组的维度的,而C语言就没有这个优化特性,所以C语言的const修饰的变量是无论如何都不能进行数组维度的定义的(注意,C++中不进行常量折叠的情况下,const修饰的仍无法进行数组定义,具体什么时候常量折叠请参看我的博文http://www.cnblogs.com/yanqi0124/p/3795019.html)。而由于其存储区域没有发生本质的改变,因此仍然可以通过其他方式改变其值,比如指针。如图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 非法写入引发的段错误

二.静态区:

  静态区是一个抽象笼统的概念,在实际的Linux/C的可执行程序中并没有静态区这个区域,具体来讲它主要由两个段组成:.data段和.bss段。其中.data段就是程序的数据段,在采用段式内存管理的架构中,数据段(data segment)通常是指用来存放程序中已初始化且不为0的全局变量或静态变量的一块内存区域。相反,BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的或初始化为0的全局变量或静态变量的一块内存区域。.data段在程序编译期间其大小及数据被确定,而.bss段则没有直接分配空间而是由编译器在.data段之后为其预留空间,在程序装载进内存时被正式分配。尽管静态区由两个不同的段组成,但是在程序链接并装载进内存之后这两段不做区分,因此我们在这里不做分开讨论。

  静态区的变量拥有以下特征:

1) 生命周期长,直到该进程结束随进程空间一起被系统回收。

2) 只初始化一次,它的空间数据在编译期间被初始化,逻辑地址在链接期间固定。

那么哪些变量将被放在静态区呢?

1.全局变量:

  顾名思义它是全局的公用的,如果一个变量被定义为全局的,那么在同一个程序中,任何函数都可以去访问、存取该变量的数据。基于此,全局变量除拥有静态区变量的全部特征之外还具有作用域广的特点,其作用域在整个程序中(可以由多个源文件组成)全局可见。

2.静态变量

  从字面上理解所谓静态变量就是被static关键字修饰的变量,只要被static修饰为静态变量那么都将被编译器分配在静态区,其也就拥有了静态区变量的全部特征。静态变量分两种:全局静态变量和局部静态变量。无论哪种只要被static修饰都将放在静态区,拥有静态区变量的全部特征。其区别仅在于作用域:如果是全局静态变量,那么该变量的作用域被限定只能在本源文件内使用(编译之后该变量的符号将不允许对外链接,但是仍然可以通过指针去间接访问);如果是局部变量则没有变化(仅限函数内部使用)。

  下面给出一段示例代码用以说明静态变量的特性(图2-1):

图 2-1

  代码中定义了一个全局变量gvalue和局部变量lvalue,并且经过两次函数调用。gvalue由于是全局变量被编译器分配在静态区,而lvalue是局部变量放在栈区。由于静态区的特写导致gvalue经过两次函数调用实现了累加,而局部变量lvalue则每次在函数调用时都被重新初始化。地址(图2-2)。

图 2-2

  下面更改程序,将局部变量修改为静态局部变量(图2-3):

图 2-3

  则由于局部变量lvalue被static修饰放在了静态区只初始化一次,因此也实现了累加(图2-4)。

图 2-4

原文出处:

浅谈C语言的数据存储(一):http://www.embedu.org/Column/Column540.htm

浅谈C语言的数据存储(二) :http://www.embedu.org/Column/Column558.htm

【转】C数据存储(包括const存储在哪,C++不同部分我在文中用红字已指出)

时间: 2024-09-30 07:34:28

【转】C数据存储(包括const存储在哪,C++不同部分我在文中用红字已指出)的相关文章

为全球数据中心提供创新存储方案

为全球数据中心提供创新存储方案 -PMC公司副总裁兼存储事业部总经理Travis Karr于CCCC演讲实录- 本次演讲涉及几个方面的内容.首先是中国数据中心存储增长的源动力,另外就是全球其他地区的数据中心看到的共同发展趋势.依据这些趋势,进一步针对具体案例做出详细分析与阐述.最后就PMC在全球数据中心创新技术研发方面的概况做一个简短介绍. 凭借巨大的人口受众和极其强有力的经济发展,加上互联网领域的创新,中国其实已经在大数据时代占据了独领风骚的地位.在存储领域,中国的存储销售量甚至已经逐渐超越美

Android数据的四种存储方式

很清晰的思路,转自Android数据的四种存储方式 作为一个完成的应用程序,数据存储操作是必不可少的.因此,Android系统一共提供了四种数据存储方式.分别是:SharePreference.SQLite.Content Provider和File.由于Android系统中,数据基本都是私有的的,都是存放于“data/data/程序包名”目录下,所以要实现数据共享,正确方式是使用Content Provider. SQLite: SQLite是一个轻量级的数据库,支持基本SQL语法,是常被采用

java数据的5种存储位置(转)

任何语言所编写的程序,其中的各类型的数据都需要一个存储位置,java中书的存储位置分为以下5种: 1.寄存器 最快的存储区,位于处理器内部,但是数量及其有限.所以寄存器根据需求自动分配,无序人为控制. 2.栈内存 位于RAM中,通过堆栈指针可以从处理器中获得直接支持.堆栈指针向下移动,则分配新的内存:向上移动,则释放哪些内存.这种存储方式仅次于寄存器.(常用于存放对象引用和基本数据类型,而不用于存储对象) 3.堆 一种通用的内存池,也位于RAM中.其中存放的数据由JVM自动进行管理. 堆相对于栈

android数据存储_外部存储

源码下载(免下载积分):下载 外部存储并不是一定可以访问的,例如外部存储挂载到电脑上是,或者是SD Card作为外部存储,被移除是,因此在访问外部存储时,一定要保证外部存储是可以获得的.判断外部存储是否已经挂载到了手机上可以这样判断: //判断外部存储是否可以访问,并且可以读写 private boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (Enviro

Atitit.各种 数据类型 ( 树形结构,表形数据 ) 的结构与存储数据库 attilax 总结

Atitit.各种  数据类型 ( 树形结构,表形数据  ) 的结构与存储数据库 attilax  总结 1. 数据结构( 树形结构,表形数据,对象结构 ) 1 2. 编程语言中对应的数据结构 java c# php ( Dic/Map      List    datatable ) 1 3. 数据库存储数据  1 4. 数据的表形显示( 多条记录 与单条记录 ) 2 5. ASP.NET 数据控件:GridView,DataList,Repeater ,DetailsView,FormVie

基于ini配置文件实现多维数组数据的按行存储和读取

需求是为一个多维数组对象的数据按行存储到文件,需要键值对区分层级,对每个对象描述清晰.类似的格式如下: 上图中的数据对应的就是如下的数组(php 语言): $arr = array( '10003' => array( 'id' => 10003, 'tokentime' => 400), '10005' => array( 'id' => 10005, <p> 'cookie' => array(</p> 'num' => 20 ), '

android 开发-数据存储之文件存储

android的文件存储是通过android的文件系统对数据进行临时的保存操作,并不是持久化数据,例如网络上下载某些图片.音频.视频文件等.如缓存文件将会在清理应用缓存的时候被清除,或者是应用卸载的时候缓存文件或内部文件将会被清除. 以下是开发学习中所写的示例代码,以供日后查阅: xml: 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="

20200113(数据加载、存储和文件格式)

6. 数据加载.存储和文件格式 输出输出通常包括几个大类: 读取文本文件和其他更高效的磁盘存储格式: 加载数据库中的数据: 利用Web API 操作网络资源. 这里着重介绍pandas的输入输出对象. 6.1  读取文本格式的数据 pandas 提供了一些用于将表格数据读取为DataFrame 对象的函数.如下: 上面这些函数的选项可以划分为以下几个大类: 其中,类型推断(type inference)是这些函数中最重要的功能之一,也就是说不需要指定列的类型到底是数值.整数.布尔值还是字符串.

[转][Android]Android数据的四种存储方式

android.database.sqlite类 SQLiteQueryBuilder java.lang.Object android.database.sqlite.SQLiteQueryBuilder public class SQLiteQueryBuilderextends Object This is a convience class that helps build SQL queries to be sent to SQLiteDatabase objects. 构造方法摘要