为什么 extern 使用 const 修饰的变量会编译不过?

const 变量能被其他文件 extern 引用吗?为什么?

先来看一段代码:

// 来源:公众号编程珠玑
// main.cc
#include<stdio.h>
// 引用外部定义的const_int变量
extern const int const_int;
int main()
{
    printf("const_int:%d\n",const_int);
    return 0;
}
// const.cc
// 定义const 变量
const int const_int  = 10;


编译链接:

 $ g++ -o main main.cc const.cc
 /tmp/ccWHAXxB.o: In function `main':
main.cc:(.text+0x6): undefined reference to `const_int'
collect2: error: ld returned 1 exit status


我们发现出现了链接问题,说 const_int 没有定义的引用,但我们确实在 http://const.cc 文件中定义了。

我们去掉 const 修饰符再编译一次,发现是可以的。从上面这个编译问题,就引出今天要讲的内容了。至于为什么会编译不过,最后再做解释。

当然你会发现,按照 C 代码去编译,是可以编译出来的。后面再解释。

链接属性

我们都知道,C/C++ 代码的编译通常经过预编译,汇编,编译,链接(参考 hello 程序是怎么生成的)通常会有变量会有三种链接属性:外部链接,内部链接或无链接

具有函数作用域,块作用域或者函数原型作用域的变量都是无链接变量;具有文件作用域的变量可以是内部链接也可以是外部链接。而外部链接变量可以在多个文件中使用,内部链接变量只能在一个编译单元中使用(一个源代码文件和它包含的头文件)。

关于作用域,也可以参考《全局变量,静态全局变量,局部变量,静态局部变量》。
说了这么多,举个具体的例子:

 // 来源:公众号【编程珠玑】
 // 作者:守望先生
 #include<stdio.h>
 int external_link = 10;  // 文件作用域,外部链接
 static internal_link = 20; // 文件作用域,内部链接
 int main()
 {
     int no_link = 30;   // 无链接
     printf("%d %d %d \n",external_link,internal_link,no_link);
     return 0;
 }


这里无链接变量还是比较好区分的,只要不是文件作用域的变量,基本是无链接属性。而文件作用域变量是内部链接还是外部链接呢?只要看前面是否有 static 修饰即可。当然对于 C++,还要看是否有 const 修饰,后面我们再说。

如何知道某个变量是什么链接属性?

举个例子,在前面的代码中,我们按照 C 代码进行编译:

 $ gcc -c const.c 
 $ nm const.o |grep const_int
 0000000000000000 R const_int


nm 命令在《linux 常用命令 - 开发调试篇》中略有介绍,它可以用来查看 ELF 文件的符号信息。

从这里的结果可以看到 const_int 前面是 R 修饰的,
R:该符号位于只读数据区,READONLY 的含义

该字母大写,其实也是表示它具有外部链接属性

再看看按照 C++ 代码编译:

 $ g++ -c const.c
 $ nm const.o |grep const_int
 0000000000000000 r _ZL9const_int


可以看到,它的修饰符也是 r,但是是小写的 r,小写字母表示该变量具有内部链接属性

nm 命令非常实用,但本文不是重点。

const 关键字

说到 const 关键字,在《const 关键字到底该怎么用》和《C++ 中的 const 与 C 中的 const 有何差别?》中已经分析过了,这里简单说一下,被 const 关键字修饰的变量,表明它是只读的,不希望被修改。

extern 关键字

extern 关键字可以引用外部的定义,想必很多朋友已经很熟悉了,举个例子,如果把最开始的例子中的 const 关键字去掉,http://main.cc 中的 extern 的意思,就是说有一个 const_int 变量,但是它在别的地方定义的,因此这里 extern 修饰一下,这样在链接阶段,它就会去其他的编译单元中找到它的定义,并链接。

当然,还有一个不太被关注的作用是,在 C++ 中,它可以改变 const 变量的链接属性。

是的,在 C++ 中,它改变了 const_int 的链接属性。我们可以修改 const.c 的内容如下:

 extern const int const_int  = 10;


然后再查看一下:

  $ nm const.o |grep const_int
 0000000000000000 R const_int


发现没有,它前面的修饰变成大写的 R 了,所以这个时候,你再编译,就能编译过,而不会报错了,对于 C,它本来就是外部链接属性,所以根本不会报错。

extern 还有另外一个用法:
C++ 是如何调用 C 接口的》?

解疑

所以,链接报错的通常问题就是找不到定义,原因无非就是:

  • 未定义
  • 在其他地方定义了,但是不具备外部链接属性
  • 定义了,具备外部链接属性,但是链接顺序有问题

由于在 C++ 中,被 const 修饰的变量默认为内部链接属性,因为链接会找不到定义。

总结

本文从一个编译问题,引出了很多内容,包括:

上一篇:C语言:存储类


下一篇:idea上运行项目出现类似错误:程序包lombok.extern.slf4j不存在(转)