嵌入式 Linux C语言(八)——存储类型、作用域、生命周期、链接属性

嵌入式 Linux C语言(八)——存储类型、作用域、生命周期、链接属性

一、存储类型

C语言中,每个变量和函数都有两个属性:数据类型和数据的存储类型。

变量的存储类型是指存储变量值的内存类型。变量的存储类型决定变量何时创建、何时销毁以及它的值将保持多久。计算机中有三个地方可以用于存储变量:普通内存,运行时堆和栈,硬件寄存器。变量的存储类型取决于声明变量的位置。

C语言存储类别说明符:


说明符


用    法


auto


只在代码块内变量声明中被允许, 表示变量具有本地生存期


extern


出现在顶层或块的外部,函数与变量声明中,表示声明的对象具有静态生存期, 链接器知道其名字


static


可以放在函数与变量声明中,在函数定义时,只用于指定函数名,而不将函数导出到链接器,在函数声明中,表示其后边会有定义声明的函数。在数据声明中,总是表示定义的声明不导出到链接器


register


要使变量尽量存储于寄存器,只能声明自动变量

1、静态变量

在代码块之外声明的变量存储于静态内存中,不属于堆和栈的内存,这类变量称为静态(static)变量。静态变量在程序运行之前创建,是在将可执行文件加载到内存的时候创建,其在程序的整个执行期间始终存在。

extern:用于声明全局变量、函数,主要用于在一个文件中定义全局变量、函数,而在另一个文件中引用全局变量、函数。一般来说,函数声明一般放在一个头文件中,供其他文件引用。

static:两种用法含义截然不同

A、修饰局部变量,静态局部变量。静态局部变量和非静态局部变量区别在于存储类不同。非静态局部变量存储在栈上,静态局部变量分配在数据段或bss段静态内存中。静态局部变量的生命周期和全局变量相同,但作用域和链接属性不同,静态局部变量的作用域为代码块作用域,链接属性为无链接;全局变量的作用域为文件作用域,链接属性为外链接。

B、修饰全局变量,静态全局变量。静态全局变量和非静态全局变量的区别在于链接属性不同,静态全局变量为内链接,非静态全局变量为外链接。

2、自动变量

在代码块内部声明的变量的缺省存储类型是自动的 (automatic),存储于栈中,称为自动变量。关键字auto就是用于修饰这种存储类型的,代码块中的变量缺省情况下就是自动变量。在程序执行到声明自动变量的代码块时,自动变量才被创建,当程序的执行流离开代码块时,代码块内创建的自动变量便自行销毁。在代码块内部声明的变量,如果给它加上static,可以使它的存储类型从自动变为静态变量。但是,修改变量的存储类型并不表示修改改变量的作用域,变量的作用域仍然是代码块内部。函数的形式参数不能声明为静态,因为实参总是在堆栈中传递给函数。

函数的形式参数不能声明为静态,因为函数的参数传递是通过堆栈进行的,用于支持递归。

auto:

修饰局部变量表示自动局部变量,自动局部变量分配在栈上,默认定义的普通局部变量。

3、寄存器存储

要使变量存储于寄存器声明自动变量时需要关键字:register。

register:

register修饰的变量编译器会尽量分配在寄存器上,不保证一定会分配在寄存器中,一般的变量分配在内存中。register修饰的变量读写效率较高,用于高频次访问的变量。

C语言程序运行时有一定要求,C语言程序无法直接在内存中运行,需要外部一定的协助,协助的代码叫加载运行代码,主要作用是给全局变量赋值、清除bss段。裸机程序开发中需要。

二、作用域

作用域是指允许对标识符进行访问的位置范围。

C99规定,C语言的作用域共有 4 种类型:文件作用域、代码块作用域、函数作用域、函数原型作用域。编译器通过变量声明的位置来确定作用域。


类型


位置


说明


文件作用域

(file)


在所有代码块和参数列表之外


整个文件内都可以访问


代码块作用域 ( block)


在“代码块”或者“函数的参数列表”内部


只有所在的代码块内可以访问


函数作用域 (function)


函数体内


具有此作用域的只有一种语句:只有goto语句要使用的“语句标签”。简化为一条规则:一个函数中的语句标签(label)必须唯一。


函数原型作用域

(function prototype)


声明的函数原型的参数列表中(注意与“函数定义”不同)


由于函数原型的参数名称可以省略,即使不省略,也不要求和“函数定义”中的形参列表中名称相同。

1、文件作用域

代码块之外声明的标识符具有文件作用域,文件作用域的范围是从标识符声明处一直到文件的结束。如果声明在头文件中,并且头文件被其他文件用#include 所包含,标识符的作用域也会相应的扩大到包含文件的结束。

2、代码块作用域

一对花括号之间的所有语句称为代码块作用域。在代码块开始位置声明的标识符具有代码块作用域。在一个代码块作用域开始定义的变量可以被该代码块内的所有语句使用。

如果代码块之间有嵌套,那么内层代码块的标识符就会把外层代码块的同名标识符掩藏,对内层代码块标识符的修改不会影响外层代码块的同名标识符;内层代码块可以使用外层代码块的标识符。

对于非嵌套的两个代码块,两个代码块之间没有交集,那么一个代码块内的语句不能使用另一个代码块内的变量。

函数定义中的参数是代码块作用域。

3、函数作用域

只适用于goto语句的语句标签。函数作用域只适用于语句标签,语句标签用于goto语句。一个函数作用域内的语句标签必须唯一

4、函数原型作用域

在函数原型中声明的参数名具有函数原型作用域。

5、标识符的命名空间

命名空间是为了解决在相同作用域内如何区分相同的标识符。
        A、只有在相同作用域的情况下才能使用到命名空间去区分标识符,在嵌套的作用域、不同的作用域区分标识符都不会用到命名空间的概念。
        B、在相同的作用域内,如果命名空间不同,标识符可以使用相同的名称。否则,即如果命名空间不同,编译器会报错,提示重复定义。

C99规定C语言命名空间可以分为四种:

A、所有的标签(label)都属于同一个命名空间。
           在同一个函数内,标签不能相同。

在同一个函数内,标签可以和其他变量名称相同。因为它们所属的命名空间不同。

B、struct、enum和union的名称属于同一个命名空间

C99中将struct、enum和union的名称称之为tag,所有的tag属于同一个命名空间。 也就是说,如果你已经声明struct A { int a }; 就不能在声明 union A{ int a };

C、struct和union的成员各自属于一个命名空间,而且是相互独立的

例如:如果你已经声明struct A { int a }; 其成员的名称为a,你仍然可以声明 struct B{ int a };或者union B{ int a };

struct和union的成员各自成为一个命名空间,是因为它们的成员访问时,需要通过 "."或"->"运算符,而不会单独使用,所以编译器可以将它们与其他的标识符区分开。由于枚举类型enum的成员可以单独使用,所以枚举类型的成员不在这一名称空间内。

D、其他所有的标识符,属于同一个名称空间。

包括变量名、函数名、函数参数,宏定义、typedef的类型名、enum的成员 等等。

6、重名标识符的处理  

如果标识符出现重名的情况,宏定义覆盖所有其它标识符,这是因为它在预处理阶段而不是编译阶段处理。除了宏定义之外其它类别的标识符,处理规则是:内层作用域会隐藏掉外层作用域的标识符。

同名变量的掩蔽规则:

两个同名变量的作用域没有重叠,则两个同名变量互不影响。

两个重名变量的作用域有重叠,则作用域小的变量掩蔽掉作用域大的变量,遵循就近原则。

C89标准的编译器中,所有的局部变量必须先定义在函数的前面,在C99标准的编译器中可以允许在代码块内任意位置定义局部变量,但也必须先定义再使用。

三、生命周期

变量的生命周期是指程序运行期间,变量从分配到地址到地址被释放的过程。根据变量的存储类型可以将变量的生命周期分为:静态生存期、自动生存期、动态分配生存期。

1、静态生存期

属于文件作用域(即external或internal链接属性)、以及被static修饰的变量,具有static静态生存期。静态生存期的变量存储在静态内存中。静态存储的变量,在程序运行之前就已经创建,在程序整个执行期间一直存在,如果声明时没有被显式的初始化,就会被自动初始化为0。

静态变量当然是属于静态存储方式,但是属于静态存储方式的变量不一定就是静态变量,例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由static加以定义后才能成为静态变量。

const常量、字符串常量存储在代码段或只读数据段,取决于平台。

2、自动生存期

链接属性为none,并且没有static修饰的变量,具有automatic自动生存期。

自动生存期的变量存储于栈或寄存器中。其中在代码块内部声明的变量,在C语言书籍中也被称为“自动变量”,使用auto修饰符,默认可以省略。对于自动存储的变量当程序执行到含有自动变量的代码段时,自动变量才被创建,并且不会被自动初始化,代码段执行结束,自动变量就自动销毁,释放掉内存。如果代码段被反复执行,那么自动变量就会反复被创建和销毁。注意这一点和静态变量不同,静态变量只创建一次,到程序结束才销毁。

3、动态分配生存期

使用malloc函数,在进程的堆空间分配内存的变量。

动态分配生存期的变量存储于堆中,也不会被自动初始化,使用free函数释放内存。

四、链接属性

链接属性是为了说明在不同文件中出现的相同标识符应该如何处理。

完整、大型、商业的C语言工程由多个c文件和h文件组成的。编译时将文件编译成.o的二进制文件,通过链接将多个.o文件链接成一个可执行程序。编译时以文件为单位,链接时以工程为单位。

各个源文件被编译后,所有的目标文件和从函数库中引用的函数(一般是归档库文件,*.a类型)经过链接器链接,形成一定格式的可执行程序,如elf格式。如果相同的标示符出现在几个不同个源文件中,他们是否表示同一个实体。这由标示符的链接属(linkage)决定。标示符的作用域和链接属性相关,作用域是由链接属性决定的,但二者并不相同。比如,内部属性的静态全局变量,在代码块内有同样名称的标示符,这时静态变量无效,起作用的是代码块内的局部变量。虽然静态全局变量具有内部链接属性,但当名字冲突时,起作用的是局部变量。

C99规定C语言的链接属性分为三种:external(外部链接), internal(内部链接), none(无链接)。


类型


说明


默认(即不使用extern和static)


外部链接external


同一个标识符,即使在不同的文件中,也表示同一个实体。


①具有文件作用域的变量和函数。 
②代码块作用域内部的函数声明


内部链接internal


同一个标识符,仅仅在同一个文件中才表示同一个实体。


如果不使用static,那么默认没有内部链接属性的标识符。只有被static修饰的具有文件作用域的标识符,才具有internal链接属性


无链接none


表示不同的实体


所有其他的标识符。如:函数的参数、代码块作用域的变量、标签等

1、外部链接

表示位于不同的源文件的相同标识符表示同一个实体。

链接属性为external的标识符不论声明多少次,位于几个原文件内均表示同一实体。函数和全局变量属于外链接。

2、内部链接

表示只有位于相同源文件的相同标识符表示同一个实体,不同源文件的相同标识符表示不同的实体。

链接属性为internal的标识符在同一个源文件内的所有声明均指向同一个实体,但位于不同源文件的多个声明则分属不同的实体。在单个文件内部进行链接,static修饰的全局变量和函数属于内链接。

3、无链接

链接属性为none的标识符不论在那个文件内都是独立的个体。

没有链接的标示符,总是被当做单独的个体,也就是说该标示符的多个声明被当做不同的实体。所有局部变量属于无链接,宏和inline函数链接属性为无链接。

在文件作用域内声明的变量或是函数,在缺省的条件下链接属性为external,其余的为none。

五、相互关系

标识符的存储类型、作用域、生命周期、链接属性是相互关联的,存储类决定生命周期,链接属性决定作用域。


作用域


声明位置


链接属性


存储类型


默认初始化值


使用static修饰


文件作用域


在所有代码块和参数列表之外


external


静态存储


0


Internal

静态存储


代码块作用域


在代码块或函数的参数列表内部


none


栈存储


形式参数调用时被初始化;代码块内部不自动初始化


None

静态存储


函数作用域


函数体内


---------


--------


标签,不需要初始化


---------


函数原型作用域


声明的函数原型的参数列表中(注意与“函数定义”不同)


---------


--------


不需要初始化

1、extern关键字

extern用来为一个标识符指定external链接属性。如果使用extern关键字声明某一个变量,说明变量是在别处定义的,可能位于别的文件也可能位于当前文件。

如果一个标识符在声明为extern前已经进行了声明,则链接属性由第一个声明决定(第一个声明的链接属性为external或internal时)。

2、static关键字

对于在代码块内部声明的变量,存储类型为自动变量,作用域为代码块作用域,链接属性为无链接,生命周期为自动生存周期。当static用于代码块内部的变量声明时,会将变量的存储类型从自动变量修改为静态变量,作用域仍然为代码块作用域,生命周期从自动生存期修改为静态生存周期,链接属性仍然为无链接。

对于在代码块外声明的全局变量,存储类型为静态变量,作用域为文件作用域,链接属性为外链接,生命周期为静态生存期。当static用于代码块外部的全局变量声明时,会将变量的链接属性从外链接修改为内链接,存储类型仍然为静态变量,生命周期仍然为静态生存周期,作用域仍然为文件作用域,但作用域将限定为本文件,不能被其它源文件使用。

对于在代码块外的函数定义,作用域为文件作用域,链接属性为外链接。当static用于代码块外的函数声明时,会将函数的链接属性从外链接修改为内链接,作用域仍然为文件作用域,但作用域将限定为本文件,不能被其它源文件使用。

对于在代码块内或代码块外部都有可能出现的情况,例如函数:函数声明的标识符为静态存储的,但是对于其形参是存储于堆栈中的,形参声明的变量作用域为原型作用域。对于存储于堆栈中的变量,即自动变量可以用register关键字使变量存储于机器硬件寄存器中。在代码块内部声明的自动变量可以通过static关键字修改为静态变量。

3、属性修改的一般原则

生命周期、存数类型都是针对变量, 因为在程序运行期间,只有变量才需要分配内存和释放内存,其他的诸如函数等都不需要。

修改变量的存储类型(如用static将自动变量变为静态变量),并不会修改变量的作用域,变量的作用域仍然有其声明的位置决定,

函数的形式参数,如果使用修饰符,只能使用register修饰,表示运行时参数存储在寄存器上。注意:形式参数是不能用auto修饰的。

六、C语言工程中标识符的使用原则

1、函数的定义与声明

在.h文件中声明的函数,如果在其对应的.c文件中有定义,那么我们在声明这个函数时,不使用extern修饰符, 如果反之,则必须显示使用extern修饰符。显示声明表示是引用外部函数,隐式声明是自己声明并定义的函数。

2、全局变量的定义与声明

全局变量应该定义c文件中并且在h文件中声明,不要定义在头文件中。

C语言的所有文件之中,只能有一个定义声明。顶层声明中,存在初始化语句是表示这个声明是定义声明,其他声明是引用声明。

所有引用声明要显示用存储类型关键字extern声明,而每个外部变量的唯一定义声明中省略存储类说明符。

所有全局变量全部以g_开头,并且尽可能声明成static类型。
尽量杜绝跨文件访问全局变量。如果的确需要在多个文件内访问同一变量,应由该变量定义所在文件内提供GET/PUT函数实现.
    全局变量必须要有一个初始值,全局变量尽量放在一个专门的函数内初始化.
如调用的函数少于三个,请考虑改为局部变量实现。

数组的引用声明:extern int G_glob[100];extern int G_glob[];

3、模块化编程

为了实现编程的模块化,一般会按功能模块对函数进行封装,封装成为.h和.c文件。

.h文件是头文件,内含函数声明、宏定义、结构体定义等内容。

.cpp文件是程序文件,内含函数实现,变量定义等内容。

为了防止对头文件的重复包含,一般采用宏定义:

#ifndef XXX
#define XXX
函数声明
#endif

参考博文:

C语言中标识符的作用域、命名空间、链接属性、生命周期、存储类型(上)(CSDN daheiantian

C语言中标识符的作用域、命名空间、链接属性、生命周期、存储类型(下)(CSDN daheiantian

C_作用域、链接属性和储存类型(CSDN Gummary)

C语言提高之——C语言中的作用域、链接属性和存储类型(CSDN 任长江)

作用域+链接属性+存储类型(博客园 graylocus)

时间: 2024-12-28 20:42:08

嵌入式 Linux C语言(八)——存储类型、作用域、生命周期、链接属性的相关文章

储存类生命周期链接属性作用域总结

储存类:变量在定义时的储存类型对其在程序中的生命周期.连接属性及作用域有很大的关联链接属性: 无连接.内连接.外链接关键字:1.auto    用于修饰局部变量,在定义局部变量时,将其定义在栈上.普通局部变量存放在栈上,因为栈内存的特性决定了普通局部变量定时如果未初始化其的值是随机的,因为栈内存是脏的,变量在释放后并未对其初始化,且栈内存被广泛运用,也就造就了普通变量的特性.普通局部变量的生命周期是临时,可从其存放于栈上看出其临时的特性.普通局部变量的作用域为代码块中"{}",C89与

嵌入式linux C++语言(四)——类与对象

嵌入式linux C++语言(四)--类与对象 类的设计和使用如下: #include <iostream>#include <stdlib.h>#include <stdio.h>#include <string.h>using namespace std;class Stack{public:    Stack(int size=1024);    ~Stack();    void init();    bool isEmpty();    bool

嵌入式 Linux C语言——C语言基础

嵌入式 Linux C语言--C语言基础 一.数据类型 1.基本数据类型 数据类型是创建变量的模型.变量名是连续存储空间的别名,程序中使用变量命名存储空间,通过变量可以使用存储空间.变量所占的内存大小取决于创建变量的数据类型. 2.有符号和无符号 有符号数中数据类型的最高位用于标识数据的符号,最高位为1表示为负数,最高位为0表示为正数. 计算机中有符号数通常使用补码表示,正数的补码为正数本身,负数的补码为负数的绝对值的各位取反后加1. 计算机中无符号数通常使用原码表示,无符号数默认为正数,没有符

嵌入式linux C++语言(二)——C++对C语言基础语法的扩展

嵌入式linux C++语言(二)--C++对C语言基础语法的扩展 C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面. 一.类型增强 1.类型检查更严格 在C语言中: const int a = 100; int *p = &a; 在C++语言中: const int a = 100;//必须在定义的时候初始化 const int *p = &a; 在C++语言中不能隐式转换数据类型. error: invalid conversion

嵌入式Linux C语言(三)——指针与函数

嵌入式Linux C语言(三)--指针与函数 指针对函数的功能有巨大的贡献,指针能够将数据传递给函数,并且允许函数对数据进行修改.指针对于函数的作用主要有两方面:将指针传递给函数和声明函数指针. 一.程序的栈和堆 程序的栈和堆是C语言程序运行的运行时元素. 1.程序栈 程序栈是支持函数执行的内存区域,通常和堆共享一块内存区域,通常程序栈占据内存区域的下部,堆用内存区域的上部.程序栈存放栈帧,栈帧存放函数参数和局部变量.调用函数时,函数的栈帧被推倒栈上,栈向上长出一个栈帧,当函数终止时,函数的栈帧

嵌入式linux C++语言(七)——继承与派生

嵌入式linux C++语言(七)--继承与派生 一.继承 在C++编程中软件可重用性(software reusability)是通过继承(inheritance)机制来实现的.类的继承,是新的类从已有类那里得到已有的特性.或从已有类产生新类的过程就是类的派生.原有的类称为基类或父类,产生的新类称为派生类或子类. 派生类的声明:class 派生类名:[继承方式] 基类名{派生类成员声明:};    一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为单继承. 继承方式规

嵌入式 Linux C语言(九)——C语言的安全问题和指针陷阱

嵌入式 Linux C语言(九)--C语言的安全问题和指针陷阱 C语言是灵活度和自由度较大的编程语言,作为C语言核心的指针更是让C语言程序员可以越过安全的栅栏,对某些内存区域进行破坏性访问,引发安全风险.很多安全问题都能追根溯源到指针的误用.本文将从指针的角度解读C语言常见的安全问题和指针陷阱. 一.指针的声明和初始化 1.不恰当的指针声明 int* ptr1, ptr2;//声明ptr1为int指针,ptr2为整型 int *ptr1, *ptr2;//ptr1,ptr2都声明为指针 #def

嵌入式Linux C语言(二)——指针

嵌入式Linux C语言(二)--指针 指针是C语言中广泛使用的一种数据类型,是C语言的灵魂.指针提供了动态操控内存的机制,强化了对数据结构的支持,而且实现了访问硬件的功能.学习指针是学习C语言中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志. 一.指针的概念 在计算机中,所有的数据都是存放在内存中的,一般把内存中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不一样,如int占用4个字 节,char占用1个字节.为了正确地访问内存单元,必须为每个内存单元编上号.

嵌入式linux C++语言(一)——C++简介

嵌入式linux C++语言(一)--C++简介 一.C++简介 C语言作是结构化和模块化的语言,适合处理较小规模的程序.对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言并不合适.为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming)思想,支持面向对象的程序设计语言应运而生.Smalltalk 就是当时问世的一种面向对象的语言.在实践工作中,由于C语言的广泛使用,在C语言的基础上根据面向对象的思想发展了C语言,形成了C