嵌入式C必看(GCC/预处理)
1 GCC
-
GCC
概述GCC
最初的全名是GNU C Compiler随着GCC
支持的语言越来越多,它的名称变成了GNU Compiler Collection翻译官/翻译组织 -
C语言编译过程
一个 C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等 4 步才能变成可执行文件
-
GCC编译选项
2 预处理
-
预处理概述
预处理指令是一些特殊的命令,用于在编译过程中进行文本替换、条件编译、宏展开等操作,从而对源代码进行预处理。预处理指令以
#
符号开头,并且在其后可以包含关键字、标识符、参数和参数值,形成一种类似于宏的语法。预处理指令是在编译过程中第一个被处理的部分,它会在实际编译之前对源代码进行处理。常见的预处理指令包括
#include
、#define
、#ifdef
、#ifndef
、#if
、#else
、#endif
等。 -
常用预处理
#include
:#include
是用于将一个文件的内容包含到当前文件中的预处理指令。它通常用于包含头文件,以便在当前文件中可以使用头文件中定义的函数、变量或宏。#define
:#define
用于定义一个宏(Macro),它是一种用于在源代码中进行文本替换的方式。可以使用#define
定义常量、函数或代码块的缩写,从而在源代码中进行简单的替换操作。#ifdef
:#ifdef
是条件编译的一种形式,用于检查一个宏是否已经定义,如果已定义,则编译后面的代码块,否则跳过。#ifndef
:#ifndef
与#ifdef
相反,它用于检查一个宏是否未定义,如果未定义,则编译后面的代码块,否则跳过。#if
:#if
是条件编译的一种通用形式,可以通过在后面跟随表达式进行条件判断,如果表达式为真,则编译后面的代码块,否则跳过。#else
:#else
是在条件编译中的备选分支,它与#if
、#ifdef
或#ifndef
配套使用,用于在条件不满足时执行备选代码块。#endif
:#endif
用于关闭一个条件编译块,与#if
、#ifdef
或#ifndef
配套使用,表示条件编译块的结束。
//括号的区别 #define ABC 5+3 printf("the %din",ABC*5)==5+3*5 #define ABC (5+3) printf("the %din",ABC*5)==(5+3)*5
-
预定义宏
预定义宏(Predefined Macros)是在C和C++编译器中预先定义好的一些宏,可以在源代码中直接使用,用于获取一些与编译环境、操作系统、编译器版本等相关的信息。这些预定义宏在编译期间会被编译器替换成对应的值,从而在编译时获取到一些编译环境的信息。
以下是一些常见的预定义宏及其用途:
-
__FILE__
:表示当前源文件的文件名(包括路径)。 -
__LINE__
:表示当前源文件中的行号。 -
__DATE__
:表示当前编译的日期,格式为字符串 “MMM DD YYYY”。 -
__TIME__
:表示当前编译的时间,格式为字符串 “HH:MM:SS”。 -
__STDC__
:表示当前编译器是否符合C语言标准的宏,如果符合,则值为1,否则未定义。 -
__cplusplus
:表示当前编译器是否符合C++语言标准的宏,如果符合,则值为一个整数,表示C++的版本号。 -
__func__
:表示当前函数的名称,C++中可以使用__PRETTY_FUNCTION__
宏来获取更详细的函数签名信息。
这些预定义宏可以在源代码中直接使用,例如可以在代码中输出
__FILE__
和__LINE__
的值来进行调试信息输出,或者根据__cplusplus
的值来进行条件编译,以适配不同的C++语言标准。需要注意的是,预定义宏的命名规则通常以双下划线开头和结尾,以避免与用户定义的宏冲突。 -
-
宏定义下的# ##
#
操作符:在宏定义中,#
操作符用于将宏参数转换为字符串常量。它可以在宏定义中的参数前面使用,并将参数的值转换为字符串,从而形成一个字符串常量//当这个宏被调用时,#x 将会把传入的参数 x 转换为一个字符串字面量。例如,STR(hello) 将会展开为 "hello"。 #define STR(x) #x int main() { int num = 42; const char* str = STR(num); printf("%s\n", str); // 输出 "num" return 0; }
##
操作符:##
操作符用于将两个宏参数合并成一个单独的标识符。它可以在宏定义中的参数之间使用,并将其合并为一个标识符,从而形成一个新的标识符//当这个宏被调用时,x##y 将会把传入的参数 x 和 y 进行连接。例如,CONCAT(x, y) 将会展开为 ab。 #define CONCAT(x, y) x ## y int main() { int num1 = 42; int num2 = 99; int num3 = CONCAT(num, 1); // 相当于 num1 int num4 = CONCAT(num, 2); // 相当于 num2 printf("%d, %d\n", num3, num4); // 输出 "42, 99" return 0; }
#
和##
运算符在宏展开时的使用有一些限制和规则:#
运算符只能用于将宏参数转换为字符串字面量,不能用于连接标识符。##
运算符只能用于连接标识符,不能用于将宏参数转换为字符串字面量。#
和##
运算符可以在同一个宏定义中多次使用,但它们之间不能有空格。#
和##
运算符在宏展开时的操作是在编译期进行的,而不是在运行时。#
和##
运算符在宏展开时的行为可能因编译器和语言标准而异,因此在使用时需要注意其兼容性。
使用
#
和##
运算符可以在宏展开时进行字符串操作和标识符操作,从而实现更灵活和复杂的宏定义和使用。