static、extern不能同时作用一个变量,extern示例
preface使用通过extern 声明 的变量 “a variable declared with extern”一、extern和static同时作用一个变量extern static int a是非法的1extern和static属于storage-class specifiers存储类说明符。根据 C 语言标准C99/C11/C17 等在一个声明中最多只能出现一个storage-class specifierauto、register、static、extern、typedef等。_Thread_localC11 起是例外可以和static或extern组合使用但static和extern本身不能组合。extern static int a;会导致编译错误通常是类似下面的提示error: multiple storage classes in declaration specifiers多个存储类说明符error: static and extern are contradictorystatic 和 extern 互相矛盾或者 linkage 不一致的错误。两者含义完全冲突static文件作用域/全局作用域时表示internal linkage内部链接变量只能在当前源文件translation unit内可见其他文件无法通过extern访问到它。extern表示external linkage外部链接告诉编译器这个变量的定义在其他文件中本文件只是声明declaration不分配存储。两者语义直接矛盾一个说“只能本文件用”另一个说“其他文件也能用”。因此编译器直接拒绝这种写法。正确的使用方式只用static推荐用于隐藏变量在file1中私有// file1.cstaticinta10;// 只能在 file1.c 中使用只用extern跨文件共享全局变量// file1.c 定义inta10;// 或 extern int a 10;极少这样写// file2.c 声明externinta;// 声明不分配空间** 关于extern int a 10 **3.a, extern 通常用来做声明表示这个变量在别的文件里定义这里只是引用它。3.b, 这样写语义极其矛盾extern 意思是“这个变量在别的地方。” 10 意思是“就在这里创建变量并赋值。”两者结合产生了语义冲突降低了代码可读性。正确的使用规范在多文件开发中标准且安全的做法是声明与定义分离3.c. 在头文件.h中声明不分配内存// file.hextern int a; // 纯声明告诉编译器别的文件有这个变量3.d 在源文件.c中定义分配内存并初始化// file.c#include file.hint a 10; // 真正的定义和初始化如果想在头文件中声明通常这样写// header.hexterninta;// 纯声明// 某个 .c 文件中定义一次inta0;特殊情况说明在函数内部块作用域static用于延长局部变量的生命周期静态存储期此时也不能和extern一起用。C 中规则类似同样不允许extern static。有些老旧或非标准编译器可能只是警告而不是错误但强烈不建议依赖这种行为属于未定义/非法写法。总结extern static int a;是错误的写法不要使用。extern和static是互斥的存储类说明符用来控制链接类型linkage语义冲突导致无法共存。二、extern关键字的示例1. 最基本的 extern 用法跨文件共享全局变量file1.c定义变量的地方#includestdio.h// 定义全局变量只定义一次intglobal_var100;voidprint_value(){printf(file1.c 中的 global_var %d\n,global_var);}file2.c使用 extern 声明#includestdio.h// 声明外部变量告诉编译器这个变量在其他文件中定义externintglobal_var;voidmodify_value(){global_var999;// 修改其他文件中的变量printf(file2.c 修改后 global_var %d\n,global_var);}main.c#includestdio.hexternvoidprint_value();// 声明其他文件中的函数externvoidmodify_value();intmain(){print_value();modify_value();print_value();// 再次打印看是否被修改return0;}编译gcc-otestmain.c file1.c file2.c2. 在头文件中使用 extern推荐的规范写法global.h#ifndefGLOBAL_H#defineGLOBAL_H// 在头文件中只做声明externintcount;externdoubletotal;voidincrement_count(void);#endifglobal.c#includeglobal.h// 这里才是真正的定义分配内存intcount0;doubletotal0.0;voidincrement_count(void){count;}main.c#includestdio.h#includeglobal.hintmain(){increment_count();increment_count();printf(count %d\n,count);// 可以直接使用printf(total %.2f\n,total);return0;}3. extern 声明数组的例子data.hexternconstchar*error_messages[];externconstintMAX_ERROR;data.c#includedata.hconstchar*error_messages[]{Success,File not found,Permission denied,Out of memory};constintMAX_ERROR4;4. extern 配合函数的例子utils.hexternintadd(inta,intb);//文件作用域的函数名默认有external linkage(外部链接属性)externvoidprint_hello(void);utils.c#includestdio.h#includeutils.hintadd(inta,intb){returnab;}voidprint_hello(void){printf(Hello from utils.c!\n);}重要注意事项定义Definition和声明Declaration的区别int a 10;→ 定义有内存分配extern int a;→ 声明只是告诉编译器变量存在只能在一个 .c 文件中定义一次其他文件用extern声明。全局变量尽量少用大型项目中推荐使用static 函数接口的方式来封装。拓展延申extern 和 static 一起使用的对比static 全局变量无法被 extern 访问extern “C” 在 C 中的用法多文件大型项目中 extern 的最佳实践三、拓展头文件里面绝对不要定义变量这是 C/C 编程中必须遵守的核心原则。原因多重定义错误头文件会被多个 .c 或 .cpp 文件 #include包含。编译器展开包含头文件等于把头文件内容直接复制到源文件中。链接器报错链接时每个包含该头文件的源文件都会生成一个同名变量导致链接器报 multiple definition重复定义错误。正确的替代方案如果你需要在多个文件之间共享变量有且只有以下几种标准做法方案一标准“声明定义”法最通用头文件.h只做声明不分配内存。// global.hexternintglobal_counter;源文件.c/.cpp做定义分配内存并初始化。// global.cintglobal_counter0;方案二C17 內联变量仅限 C直接在头文件定义加入 inline 关键字。原理链接器会自动去重保证全程序只有一个实例。// global.hinline int global_counter 0;方案三静态局部变量单例C/C 通用原理用函数包裹变量头文件只提供函数声明。// 头文件 global.hint*get_counter();// 源文件 global.cint*get_counter(){staticintcounter0;returncounter;}唯一的例外静态常量在头文件中定义 const 或 constexprC变量是允许的。因为它们默认具有内部链接属性每个源文件会各自复制一份不会引发链接冲突。// 头文件 config.h 中可以这样写constintMAX_BUFFER_SIZE1024;四、编译、链接跨文件共享全局变量a1、跨文件例子project/ ├── main.c ├── a.c └── a.h1.1a.c真正定义变量// a.cinta10;这句是定义。意思是在这个文件里真正创建一个全局变量a并初始化为 10。它会让目标文件里出现一个符号比如a并且这个符号表示一块真实的存储空间。1.2.a.h声明变量// a.h#ifndefA_H#defineA_Hexterninta;#endif这里是声明。意思是有一个叫a的int变量它定义在别的地方。当前文件如果包含这个头文件就可以使用它。注意这里不能写inta;更不能写inta10;因为头文件会被多个.c文件包含如果在头文件里定义变量很容易导致重复定义。1.3.main.c使用变量// main.c#includestdio.h#includea.hintmain(void){printf(a %d\n,a);a20;printf(a %d\n,a);return0;}编译运行gcc main.c a.c-oapp ./app输出a 10 a 202、这三个文件各自的角色可以这样理解// a.cinta10;这是告诉编译器和链接器我这里真的有一个变量a。请给它分配空间并初始化为 10。而// a.hexterninta;这是告诉编译器你看到a的时候别报错。它是一个int类型的全局变量。但是它的实体不在这里链接阶段会去别的目标文件里找。3、编译和链接时内部发生了什么把命令拆开看gcc-cmain.c-omain.o gcc-ca.c-oa.o gcc main.o a.o-oapp整个过程大概分成两步main.c --- main.o a.c --- a.o main.o a.o --- app4、编译a.c时发生了什么a.c里面有inta10;编译器看到这句会认为这是一个全局变量定义。于是生成目标文件a.o时大概会记录a.o: 定义了一个全局符号 a a 的初始值是 10如果从符号角度看a.o里类似这样Symbol table of a.o: a defined也就是说a.o 负责提供变量 a5、编译main.c时发生了什么main.c里面有#includea.h预处理后相当于变成externinta;intmain(void){printf(a %d\n,a);a20;printf(a %d\n,a);return0;}编译器看到externinta;它知道a是一个int类型的外部变量。这里可以使用它。但是当前文件不负责定义它。所以生成main.o时大概会记录main.o: 使用了符号 a 但 main.o 自己没有定义 a符号角度类似Symbol table of main.o: a undefined也就是说main.o 需要别人提供变量 a6、链接阶段发生了什么链接命令gcc main.o a.o-oapp链接器会检查每个目标文件里的符号。它大概看到main.o: 需要 a a.o: 提供 a于是链接器会把它们匹配起来main.o 中对 a 的引用 --- a.o 中定义的 a最终生成可执行文件app。运行时程序里只有一个真正的变量a。main.c中访问的a就是a.c里定义的那个a。7、如果忘记写a.c会怎样比如只有// main.c#includestdio.hexterninta;intmain(void){printf(%d\n,a);return0;}然后你只编译gcc main.c-oapp会发生什么编译阶段通常能过因为编译器看到externinta;它相信a在别的地方定义。但是链接阶段会报错undefined reference to a意思是你说a在别的地方但我链接的时候找遍了所有目标文件也没找到谁真正定义了a。8、如果在两个文件里都定义a会怎样例如// a.cinta10;// b.cinta20;再链接gcc main.c a.c b.c-oapp链接器会发现a.c 定义了 a b.c 也定义了 a这时会报错类似multiple definition of a因为全局变量a有外部链接属性整个程序里只能有一个同名定义。9、那extern int a 10;放在哪里如果在a.c中写// a.cexterninta10;这在 C 语言里也相当于一个定义。它和inta10;效果基本一样。也就是说externinta10;不是“只是声明”因为它带了初始化。你可以理解成extern想表达“这个名字具有外部链接属性”但是 10让它变成了一个真正的定义。所以// a.cexterninta10;配合// main.cexterninta;是可以工作的。但是不推荐因为它容易误导读代码的人。推荐写法仍然是// a.cinta10;// a.hexterninta;10、千万不要在头文件里写extern int a 10;错误示例// a.hexterninta10;然后// main.c#includea.h// b.c#includea.h预处理后相当于// main.cexterninta10;// b.cexterninta10;它们都会变成定义。于是链接时相当于有两个文件都定义了amain.o 定义了 a b.o 定义了 a链接器就会报multiple definition of a所以头文件里应该只写externinta;不要写初始化。11、可以这样记最推荐的模式是// xxx.cinta10;表示这里定义变量。然后// xxx.hexterninta;表示别的文件可以引用这个变量。再由其他.c文件#includexxx.h来使用它。12、一句话总结inta10;是定义。externinta;是声明。externinta10;虽然写了extern但因为有初始化所以也是定义。跨文件使用时应该// a.cinta10;// a.hexterninta;// main.c#includea.h链接时main.o里对a的引用会被链接器绑定到a.o中真正定义的那个a。