最近在总结关于Linux系统关于Time处理相关的API,当在开发库中使用到localtime_r以及clock_gettime时,会提示如下的错误(-Werror选项打开):
error: implicit declaration of function ‘localtime_r’ [-Werror=implicit-function-declaration]
解决步骤
怀疑GCC版本
开发库使用的项目构建工具为cmake,出现这个问题很诡异,因为之前编写小的测试程序时没有问题。第一时间感觉可能是cmake的CMakeLists.txt配置 存在问题(PS:编写测试程序时直接使用的gcc),进一步感觉是不是cmake没有使用正确版本的gcc,通过查询确定一种修改cmake修改编译器的方法如下: build.sh中增加如下代码:
COMPILE_PATH="-DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/g++ -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/gcc" cmake . $COMPILE_PATH
其中,DCMAKE_CXX_COMPILER用于指定g++的路径,CMAKE_C_COMPILER用于指定gcc的路径。
重新build,cmake提示确实更换了gcc的配置:
You have changed variables that require your cache to be deleted. Configure will be re-run and you may have to reset some variables. The following variables have changed: CMAKE_C_COMPILER= /usr/bin/gcc CMAKE_CXX_COMPILER= /usr/bin/g++ -- The C compiler identification is GNU 4.8.4 -- The CXX compiler identification is GNU 4.8.4 -- Check for working C compiler: /usr/bin/gcc -- Check for working C compiler: /usr/bin/gcc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/g++ -- Check for working CXX compiler: /usr/bin/g++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done
可是,编译还是提示同样的错误,看来是错怪了cmake和gcc了。
cmake最小工程
不是gcc版本的问题,现在只剩下cmake环境和开发库环境了,首先测试cmake最小工程,是否可以编译通过(注意需要使用相同的CMakeLists.txt配置)。
工程目录其实非常简单,只包含CMakeLists.txt和一个c源文件,代码如下:
#CMakeLists.txt: #Cmake 最低版本要求 cmake_minimum_required(VERSION 3.1) #项目名称 project(emlib) add_compile_options(-std=c99 -Werror) set(CMAKE_CFLAGS_DEBUG "$ENV{CFLAGS} -O0 -Wall -g -ggdb -fstack-protector-all") set(CMAKE_CFLAGS_RELEASE "$ENV{CFLAGS} -O3 -Wall -DNDEBUG") #查找src目录下的所有源文件,将其存储到DIR_SRCS变量中 aux_source_directory(. DIR_SRCS) #配置头文件存放的目录 include_directories(/usr/include/) #生成制定目标 add_executable(${PROJECT_NAME} ${DIR_SRCS}) #配置链接库 target_link_libraries(${PROJECT_NAME} pthread)
test.c #include <time.h> int main(void) { struct tm rs; time_t tp; time(&tp); localtime_r(&tp, &rs); return 0; }
编译测试,问题仍然存在。好了,基本确定为cmake的配置问题了。但是,大家看到了,cmake的配置相当的简单,到底是哪一条配置影响到Linux系统库了呢?
柳暗花明
正当问题陷入困境时,突然想到,cmake支持生成各种编译文件的功能,何不看看源文件的预编译文件,到底是因为什么原因没有声明localtime_r函数的。通过make test.i生成 test.c的预编译文件,打开搜索发现,test.i中确实没有声明localtime_r函数,那么现在的问题就是test.c为什么没有在预编译阶段包含到time.h中的localtime_r声明?
直接打开time.h文件看看就知道了,time.h文件位于/usr/include/time.h,搜索发现如下代码:
# if defined __USE_POSIX || defined __USE_MISC /* Return the `struct tm' representation of *TIMER in UTC, using *TP to store the result. */ extern struct tm *gmtime_r (const time_t *__restrict __timer, struct tm *__restrict __tp) __THROW; /* Return the `struct tm' representation of *TIMER in local time, using *TP to store the result. */ extern struct tm *localtime_r (const time_t *__restrict __timer, struct tm *__restrict __tp) __THROW; # endif /* POSIX or misc */
很明显,localtime_r只有在__USE_POSIX 和 __USE_MISC宏定义时,才会进行声明!那么,应该是gcc为什么没有定义这些宏呢?
回头分析CMakeLists.txt文件,发现只有add_compile_options(-std=c99 -Werror)修改了编译器的选项,将其注释,重新编译,好了,编译通过。
那么问题应该是-std=c99导致的(相信大家之所以导入该选项,都是为了for循环时少写一行代码(for int i = 0; i < N; i++)),那么,该为了支持某些比较新的编译器特性,该选用 那些标准呢?
C标准
通过查询,发现历史上出现过很多C相关的标准,下面了一个表:
主版本 | C89 | AMD1 | C99 | C11 |
别名 | C90 、ANSI C、 X3.159-1989 、ISO/IEC 9899:1990 | C94 、C95 | ISO/IEC 、9899:1999 | ISO/IEC 、9899:2011 |
标准通过时间 | ||||
标准发布时间 | 1990年 | 1995年 | 1999年 | 2011年 |
GCC使用此版本所用参数 | -ansi -std=c90 -std=iso9899:1990 | -std=iso9899:199409 | -std=c99 -std=iso9899:1999 | -std=c11 -std=iso9899:2011 |
GCC使用此版本且带C扩展时所用参数 | -std=gnu90 | -std=gnu99 | -std=gnu11 |
由于我们使用的是gcc所以,-std=c99应该换为-std=gnu99,编译测试通过。
其实,gnu90、gnu99、gnu11都是标准C中扩展了GNU/GCC的一些特性,例如,类似于_USE_POSIX、_USE_POSIX宏都是在gnu标准下定义的,gcc默认情况下是使用gnu标准的,所以如果是在GNU/Linux 平台下,使用gcc开发,如果需要指定C语言标准,那么应该使用gnu相关的标准。
后记
大家看到了,本文遇到的问题不是什么特别难的编程问题,问题的原意只是因为编译器的选项问题。但是,为什么还要做这么详细的总结?其实,问题本身不重要,重要的是解决问题的方法。 本文其实展示一个解决问题的基本步骤:
- 首先,分析问题表象,如果不是很容易定位问题原因,那么我们就应该分步骤去解决它;
- 确定可能导致问题的原因域,并把它们按照可能性排序,最有可能肯定需要首先处理;
- 然后,按照原因域,分条测试,逐个排除,只到问题解决;
- 如果,测试完所有的原因域,问题都没有解决,那么可能是原因域存在漏掉的情况或者问题分析的方向存在问题或者问题现象没有分析透...
- 按照步骤4,重新定义原因域,重复2-3步骤;
其实,不管是大问题还是小问题,我们都应该以科学的方法、冷静的头脑,去分析,去解决,要相信只要肯用脑,办法总比困难多!