注:本课程参考文献《C安全编码标准》
欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~
目录
一.引言
链接能够让一个标识符在不同的作用域中被声明,或者在同一作用域中被多次声明,所有这些声明都指向同一个对象或函数。标识符可以根据其链接属性被分为三类:外部链接、内部链接和无链接。这三种链接类型各自具有独特的特性。
外部链接:在整个程序中,具有外部链接的标识符代表着相同的对象或函数,这意味着在程序的所有编译单元和库中,该标识符都指向同一个对象或函数。链接程序可以利用这个标识符进行链接。然而,如果在程序中对同一个具有外部链接的标识符进行第二次声明,链接程序将不再将其与原先的对象或函数关联。
内部链接:具有内部链接的标识符指的是,在特定的翻译单元里,它们代表相同的对象或函数。由于链接程序并不掌握关于这些内部链接标识符的任何信息,所以这些标识符的作用范围仅限于该翻译单元内部。
无链接:若标识符未建立链接,那么任何使用该标识符的后续声明都会创建新的对象,比如新的变量或新的类型。
链接按照如下方式确定:
如果一个对象或者函数的文件作用域标识符包含存储类指示符static,该标识符具有内部链接。
对于使用存储类指示符extern在某个作用域中声明的标识符,如果该标识符的前一次声明在此作用域中可见,且前一次声明指定内部或者外部链接,则后来声明的标识符链接与前一次声明相同。如果没有可见的前一次声明,或者前一次声明指定无链接,则该标识符具有外部链接。
如果函数标识符的声明没有存储类指示符,那么它的链接确定方式和使用存储类指示符extern的情况相同。如果对象的标识符声明具有文件作用域且没有存储类指示符,则标识符具有外部链接。
下列标识符没有链接:声明为对象或者函数之外的任何标识符;声明为函数参数的标识符;不使用存储类指示符extern的块作用域对象标识符。
使用分类兼具内部和外部链接的标识符(在一个翻译单元中)是未定义行为。一个翻译单元包含源文件及其头文件,以及通过预处理指令#include包含的所有源文件。
表为在一个翻译单元中声明两次的对象指定的链接方式。列方向表示第一次声明,行方向表示重新声明。
第二次声明 | ||||
静态 | 无链接 | 外部 | ||
静态 | 内部 | 未定义 | 内部 | |
第一次声明 | 无链接 | 未定义 | 无链接 | 外部 |
外部 | 未定义 | 未定义 | 外部 |
二.不安全代码
在下列不安全代码示例中,i2和i5被定义为兼具内部和外部链接。这两个标识符未来的使用会造成未定义行为。
int i1=10; /* Definition,external linkage */
static int i2=20; /* Definition,internal linkage */
extern int i3=30; /* Definition,external linkage */
int i4; /* Tentative definition,external linkage */
static int i5; /* Tentative definition,internal linkage */
int i1; /* Valid tentative definition */
int i2; /* Undefined,linkage disagreement with previous */
int i3; /* Valid tentative definition */
int i4; /* Valid tentative definition */
int i5; /* Undefined,linkage disagreement with previous */
int main(void){
/* ... */
return 0;
}
三.修复方案
下列兼容解决方案不包含冲突的定义:
int i1=10; /* Definition,external linkage */
static int i2=20; /* Definition,internal linkage */
extern int i3=30; /* Definition,external linkage */
int i4; /* Tentative definition,external linkage */
static int i5; /* Tentative definition,internal linkage */
int main(void){
/* ... */
return 0;
}
四.练习与答案
案例一
问题描述:
在一个大型项目中,开发者在两个不同的源文件中声明了同一个全局变量,其中一个使用了static
关键字,另一个则没有。这导致了链接时的未定义行为。
代码示例:
// file1.c
static int globalVar = 42; // 内部链接
// file2.c
extern int globalVar; // 外部链接
问题:
上述代码中的globalVar
变量在两个不同的文件中被声明,一个使用了static
关键字,另一个使用了extern
。这将导致什么类型的链接问题?
答案:
这将导致未定义行为,因为globalVar
在同一个程序中被赋予了内部链接和外部链接。static
关键字使得globalVar
在file1.c
中具有内部链接,而extern
关键字在file2.c
中声明了外部链接。链接器无法确定这两个声明是否指向同一个对象,因此行为未定义。
案例二
问题描述:
开发者在头文件中声明了一个全局变量,并在多个源文件中包含了这个头文件。然而,他们没有使用extern
关键字,导致每个源文件都创建了一个该变量的独立副本。
代码示例:
// header.h
int globalVar; // 无extern关键字
// file1.c
#include "header.h"
// file2.c
#include "header.h"
问题:
在上述代码中,globalVar
变量会被如何处理?每个源文件都会有自己的globalVar
副本吗?
答案:
是的,每个源文件都会有自己的globalVar
副本。因为在头文件中声明globalVar
时没有使用extern
关键字,所以每个包含该头文件的源文件都会创建一个新的globalVar
变量。正确的做法是在头文件中使用extern
关键字来声明globalVar
,并在一个源文件中定义它。
案例三
问题描述:
开发者在源文件中声明了一个静态变量,并在同一个源文件的另一个函数中尝试使用extern
关键字来访问它。
代码示例:
// file.c
static int myVar = 10; // 静态变量
void func1() {
// ...
}
void func2() {
extern int myVar; // 尝试访问静态变量
// ...
}
问题:
在上述代码中,func2
中的extern int myVar;
声明能够成功访问func1
之前声明的myVar
变量吗?
答案:
不能。myVar
在声明时使用了static
关键字,因此它具有内部链接,其作用范围仅限于声明它的翻译单元内部。在func2
中使用extern
关键字尝试访问myVar
是无效的,因为extern
用于声明具有外部链接的变量。在这种情况下,func2
中的myVar
声明实际上是一个新的、未定义的外部变量声明,与func1
中的myVar
无关。
非常感谢您花时间阅读我的博客,希望这些分享能为您带来启发和帮助。期待您的反馈与交流,让我们共同成长,再次感谢!
👇热门内容👇
Orbslam3&Vinsfusion_安城安的博客-CSDN博客
👇个人网站👇