一個問題引出的---對gcc與C語言標準思考

最近在總結關於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相關的標準。

後記

大家看到了,本文遇到的問題不是什麼特別難的編程問題,問題的原意只是因爲編譯器的選項問題。但是,爲什麼還要做這麼詳細的總結?其實,問題本身不重要,重要的是解決問題的方法。
本文其實展示一個解決問題的基本步驟:

  1. 首先,分析問題表象,如果不是很容易定位問題原因,那麼我們就應該分步驟去解決它;
  2. 確定可能導致問題的原因域,並把它們按照可能性排序,最有可能肯定需要首先處理;
  3. 然後,按照原因域,分條測試,逐個排除,只到問題解決;
  4. 如果,測試完所有的原因域,問題都沒有解決,那麼可能是原因域存在漏掉的情況或者問題分析的方向存在問題或者問題現象沒有分析透…
  5. 按照步驟4,重新定義原因域,重複2-3步驟;

其實,不管是大問題還是小問題,我們都應該以科學的方法、冷靜的頭腦,去分析,去解決,要相信只要肯用腦,辦法總比困難多!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章