GCC编译选项系列—— -Idir(指定头文件)

这个选项使用的频率比较高!用于指定头文件所在目录!

0、本文所使用环境

硬件	:龙芯3A5000
OS	:Loongnix Server 8.4

1、引言

在C/C++代码中,使用#include语句包含头文件,使用#include " " 样式时,编译器首先在编译目录下查找所引用的头文件,如果没有找到,编译器会继续在默认路径中进行查找;如果使用的是#include < >样式,则只会在默认路径下查找。

有时我们安装库的头文件没有在标准目录下(Linux中很常见),那么此时就可以使用-Idir方式来指定头文件所在目录。指定之后,编译器首先会在-I所指定的目录下去查找头文件,找不到后才会去默认路径下进行查找。

2、使用-I指定头文件所在目录

下面所用示例代码test.c引用自:https://zhuanlan.zhihu.com/p/34903301(作者原文已注明可转载)

#include <glib.h>
#include <glib/gprintf.h>

// DATA STRUCTURE
typedef struct Value_T {
	gint iKeyCopy;
	gint iItem1;
	gint iItem2;
} Value_T;

// RELASE CALLBACK
void func_destroy_notify_for_value(gpointer data) {
	Value_T* pp = (Value_T*)data;
	if (pp == NULL)
		return;
	g_print("func-destroy-notify-for-test: KEY:%d ITEM1:%d ITEM2:%d \n", pp->iKeyCopy, pp->iItem1, pp->iItem2);
	g_free(pp);
	pp = NULL;
}

// MAIN
int main (int argc, const char* argv[]) {
	gint iKey = 20160222;

	// CREATE
	GHashTable* ptHashTable = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, func_destroy_notify_for_value);
	if (ptHashTable == NULL) {
		g_print("main: create hashtable failed\n");
		return -1;
	}

	// INSERT
	if (ptHashTable) {
		Value_T* ptVal = g_new0(Value_T, 1);
		if (ptVal) {
			ptVal->iKeyCopy = iKey;
			ptVal->iItem1 = 123;
			ptVal->iItem2 = 456;
			g_hash_table_insert(ptHashTable, GINT_TO_POINTER(iKey), ptVal);
		} else {
			g_print("main: cannot get new memory to insert, error\n");
		}
	}

	// LOOKUP OK
	if (ptHashTable) {
		Value_T* pp = (Value_T*)g_hash_table_lookup(ptHashTable, GINT_TO_POINTER(iKey));
		if (pp) {
			g_print("main: lookup ok for key:%d : %d  %d\n", iKey, pp->iItem1, pp->iItem2);
		} else {
			g_print("main: lookup fail for key:%d\n", iKey);
		}
	}

	// LOOKUP FAIL
	if (ptHashTable) {
		Value_T* pp = (Value_T*)g_hash_table_lookup(ptHashTable, GINT_TO_POINTER(iKey+1));
		if (pp) {
			g_print("main: lookup ok for key:%d : %d  %d\n", iKey+1, pp->iItem1, pp->iItem2);
		} else {
			g_print("main: lookup fail for key:%d\n", iKey+1);
		}
	}

	// RELEASE
	if (ptHashTable) {
		g_hash_table_remove_all(ptHashTable);	
		g_hash_table_destroy(ptHashTable);
		ptHashTable = NULL;
	}		

	return 0;
}

在命令行中直接使用-Idir方式指定头文件所在目录,此处先忽略-l(小写L)指定库名称,这个在后续文章中再做解释。

gcc -std=c99 -g -Wall -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -lgthread-2.0 -pthread -lglib-2.0 -o test.elf test.c

如果头文件安装到非默认路径的库很多,这样一个一个去指定,这明显性就有些不太合理了。我们遇到的问题,那都是别人已经见过的了,所以在Linux中,就有这么一个工具专门来做这件事,那就是pkg-config

3、pkg-config

3.1、pkg-config常用选项

[loongson@bogon ~]$ pkg-config --cflags glib-2.0
-I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include 
[loongson@bogon ~]$ pkg-config --libs glib-2.0
-lglib-2.0

3.2、用pkg-config方式来编译前面的示例

这条编译命令同样转载自:https://zhuanlan.zhihu.com/p/34903301(作者原文已注明可转载)

gcc -std=c99 -g -Wall `pkg-config --cflags --libs glib-2.0 gthread-2.0` -o test.elf test.c

3.3、更进一步

这么做还是有些复杂了,更进一步,Linux中软件包在编译的时候,会有一个pkgsname.pc的文件,来记录这一切。而这些pkgsname.pc位于/usr/lib64/pkgconfig/目录下。

[loongson@bogon ~]$ more /usr/lib64/pkgconfig/glib-2.0.pc 
prefix=/usr
exec_prefix=/usr
libdir=/usr/lib64
includedir=/usr/include

glib_genmarshal=glib-genmarshal
gobject_query=gobject-query
glib_mkenums=glib-mkenums

Name: GLib
Description: C Utility Library
Version: 2.56.4
Requires.private: libpcre
Libs: -L${libdir} -lglib-2.0 
Libs.private: -pthread  -lpcre     
Cflags: -I${includedir}/glib-2.0 -I${libdir}/glib-2.0/include 

4、查看头文件默认搜索路径

另外在多说一句,Loongnix(CentOS8)中,查看头文件默认搜索路径的命令如下:

[loongson@bogon ~]$ echo 'main(){}' | /usr/bin/gcc -E -v -
......
ignoring nonexistent directory "/usr/lib/gcc/loongarch64-loongson-linux-gnu/8/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/loongarch64-loongson-linux-gnu/8/../../../../loongarch64-loongson-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/loongarch64-loongson-linux-gnu/8/include
 /usr/local/include
 /usr/include
End of search list.
......

5、额外的实验

在Linux下写C/C++代码的人,基本上都知道gcc将编译分为4个阶段:预处理(-E)、编译(-S)、汇编(-c)、链接。那么本文所说的查找头文件是在那一步完成的呢?

先说结论:查找头文件在预处理阶段完成。

还是以前面的示例为基础:
1)第一条命令,因为只进行了预处理,所以暂时可以不需要动态库信息。
2)第二条命令,当少了一个库所在头文件时,预处理阶段出错了

# 此条命令与上面2中完全一致,放在这里只是为了方便对比
[loongson@bogon gcc]$ gcc -std=c99 -g -Wall -E -I/usr/include/glib-2.0  -lgthread-2.0 -pthread -lglib-2.0 -o test.elf test.c
# 第一条命令
[loongson@bogon gcc]$ gcc -std=c99 -g -Wall -E -I/usr/include/glib-2.0  -I/usr/lib64/glib-2.0/include test.c > test.i
# 第二条命令
[loongson@bogon gcc]$ gcc -std=c99 -g -Wall -E -I/usr/include/glib-2.0   test.c > test.i
In file included from /usr/include/glib-2.0/glib/galloca.h:32,
                 from /usr/include/glib-2.0/glib.h:30,
                 from test.c:1:
/usr/include/glib-2.0/glib/gtypes.h:32:10: fatal error: glibconfig.h: No such file or directory
 #include <glibconfig.h>
          ^~~~~~~~~~~~~~
compilation terminated.
[loongson@bogon gcc]$ 

6、头文件相关环境变量

本章内容来自gcc官方文档

除了上面的方法,在Linux中,还有四个环境变量可以用来设置预处理阶段头文件搜索路径:C_INCLUDE_PATH(用于C语言)、CPP_INCLUDE_PATH(用于C++)、CPATH(都可以用)、OBJC_INCLUDE_PATH(这个我没用到,应该是苹果公司的那个)。

在Loongnix Server 8.4中,这三个环境变量的值均为空:

[loongson@bogon gcc]$ echo $C_INCLUDE_PATH

[loongson@bogon gcc]$ echo $CPP_INCLUDE_PATH

[loongson@bogon gcc]$ echo $CPATH

[loongson@bogon gcc]$ 

在Linux中,它们的取值可以是一组用:分割开的地址列表,类似于环境变量PATH
CPATH指定了一个要搜索的目录列表,就像用-I指定的那样,但是优先级低于使用-I指定的目录。
其余几个环境变量只适用于预处理特定的语言,每个变量都指定了一个要搜索的目录列表,就像用-isystem指定的那样,但是优先级低于-isystem所指定的目录

6.1、-isystem解释

-isystem dir
	Search dir for header files, after all directories specified by -I but before the standard system directories.  
	Mark it as a system directory, so that it gets the same special treatment as is applied to the standard system directories.

6.2、注意事项

在这些环境变量中,空元素指示编译器搜索其当前工作目录。空元素可能出现在路径的开头或结尾。例如,如果CPATH的值是:/special/include,这就相当于'-I. -I/special/include'

7、其他相关的gcc选项

7.1、-I-

这个选项的功能就是取消-Idir的设置,常用于-Idir之后。

posted @ 2022-11-19 11:26  streamlet_hy  阅读(370)  评论(0编辑  收藏  举报  来源