发现
起初是在看编译器学习仓库的第一章 README 和代码的时候发现的,头文件中的有一些函数被声明为 static
的,有一些不是。我依稀记得以前看八股文的时候,带 static
声明的函数其可见性仅限于当前的编译翻译单元,也就是编译器在进行词法分析和语法分析的时候,这里面产出的符号不会用于其他文件编译翻译时候的查找和链接了,不存在于编译产物的导出符号表中。
简单谷歌了一下看看人家怎么说的:
In C and C++, function declarations are extern by default. You’ve likely been using extern functions often without realizing it! Any time you call a function defined in another source file, you’re relying on its implicit extern declaration. In this example, we declare the add function in the math.
这是说,对于大部分的编译工具链来说,函数的声明默认是 extern
属性的,
-
extern
表示该函数或变量在其他地方定义,并且可以被多个文件共享。 -
static
表示该函数或变量仅在当前文件(或翻译单元)中可见,不能被其他文件访问。 -
extern
和static
关键字的作用是相反的,不能同时使用。 -
如果你在头文件中声明了一个函数为
extern
,然后在源文件中将其声明为static
,会导致编译错误。
通常我在写项目的时候,只会对于头文件中的全局共享变量,比如全局错误代码 errno
使用 extern
属性,避免其他文件包含该头文件相当于声明了 errno
之后,又链接了其他包含该头文件的源文件造成符号重定义。但是我从来没有考虑过,原来函数默认是 导出的 是因为编译器默认帮我们携带了 extern
关键字。那么对应的,源文件(头文件)中的变量应该默认是 static
仅当前翻译编译单元可见。
验证
写了三个简单的文件测试 static
属性函数的链接行为。首先是头文件声明函数和变量:
// calc.h
#ifndef CALC_H
#define CALC_H
extern int calc_errno;
static int inner_add(int a, int b);
int add(int a, int b);
#endif
然后是实现了该头文件声明的函数的源文件:
// calc.cpp
#include <calc.h>
int calc_errno = 0;
static int inner_add(int a, int b)
{
if (a < 0 || b < 0)
calc_errno = 1;
return a + b;
}
int add(int a, int b)
{
return inner_add(a, b);
}
这两个文件将在 CMakeLists.txt 中声明为一个动态库目标(SHARED),然后 main.cpp
在编译翻译为目标代码之后,再尝试链接这个动态库,看看找不找得到使用 static
函数的定义符号引用。
// main.cpp
#include <iostream>
#include <calc.h>
using namespace std;
int main(int argc, char** argv)
{
cout << "hello world" << endl;
cout << inner_add(-1, -1) << endl;
cout << calc_errno << endl;
return 0;
}
另外,所使用的 CMakeLists.txt 代码如下:
cmake_minimum_required(VERSION 3.20)
project(static)
message("${CMAKE_CURRENT_SOURCE_DIR}")
message("${CMAKE_CURRENT_BINARY_DIR}")
message("${CMAKE_BUILD_TYPE}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE})
include_directories(.)
add_library(calc SHARED calc.cpp calc.h)
add_executable(main main.cpp)
target_link_libraries(main calc)
在 VScode 中调用 CMake 完成配置、编译、链接,得到的终端输出如下:
* Executing task: CMake: Configure
Config task started...
/home/fredom/workspace/test/cc/static_link
/home/fredom/workspace/test/cc/static_link/build
Debug
Not searching for unused variables given on the command line.
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fredom/workspace/test/cc/static_link/build
Configure finished with return code 0
* Terminal will be reused by tasks, press any key to close it.
* Executing task: CMake: Build
build task started....
/home/fredom/bin/cmake/cmake --build /home/fredom/workspace/test/cc/static_link/build --config Debug --target all -j 22 --
[ 25%] Building CXX object CMakeFiles/calc.dir/calc.cpp.o
[ 50%] Linking CXX shared library bin/Debug/libcalc.so
[ 50%] Built target calc
[ 75%] Building CXX object CMakeFiles/main.dir/main.cpp.o
In file included from /home/fredom/workspace/test/cc/static_link/main.cpp:2:
/home/fredom/workspace/test/cc/static_link/./calc.h:6:12: warning: ‘int inner_add(int, int)’ used but never defined
6 | static int inner_add(int a, int b);
| ^~~~~~~~~
[100%] Linking CXX executable bin/Debug/main
/usr/bin/ld: CMakeFiles/main.dir/main.cpp.o: in function `main':
/home/fredom/workspace/test/cc/static_link/main.cpp:9:(.text+0x49): undefined reference to `inner_add(int, int)'
collect2: error: ld returned 1 exit status
gmake[2]: *** [CMakeFiles/main.dir/build.make:98: bin/Debug/main] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:111: CMakeFiles/main.dir/all] Error 2
gmake: *** [Makefile:91: all] Error 2
build finished with error(s).
可以看到,main.cpp
如果直接使用头文件中的 static
属性函数,那么是无法链接成功的(undefined reference to xxx
)。换成 add
函数的话,编译就没问题可以通过了。
* Executing task: CMake: Configure
Config task started...
/home/fredom/workspace/test/cc/static_link
/home/fredom/workspace/test/cc/static_link/build
Debug
Not searching for unused variables given on the command line.
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/fredom/workspace/test/cc/static_link/build
Configure finished with return code 0
* Terminal will be reused by tasks, press any key to close it.
* Executing task: CMake: Build
build task started....
/home/fredom/bin/cmake/cmake --build /home/fredom/workspace/test/cc/static_link/build --config Debug --target all -j 22 --
[ 50%] Built target calc
[ 75%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable bin/Debug/main
[100%] Built target main
build finished successfully.
此时运行编译链接成功的 main ELF 二进制可执行文件,得到输出:
hello world
-2
1
[1] + Done