#define MACRO
1.1、#define MACRO的作用

定义

2个作用:

  • 作为一个标志。
  • 作为另外一个符号(常量名、函数名等)的别名,以简化编码,在编译的时候进行替换。
1.2、#define MACRO的使用场景
1.2.1、定义一个宏,作为一个标志

示例:

#ifndef DEBUG
#define DEBUG
#endif

int main(){
    #ifdef DEBUG
        printf("debug\n");
    #endif
}

为了避免重复定义#define MACRO通常会与#ifndef MACRO结合使用。

1.2.2、定义符号常量

示例:

#define ONE 1
#define TWO 2
#define THREE (ONE + TWO)

注意:上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑,还是应该加上括号的。

1.2.3、定义符号函数

示例:

#include<stdio.h>

#define MAX(a, b) a > b ? a : b

int main() {
    int max = MAX(3, 4);
    printf("max = %d\n", max);
    return 0;
}

注意:有人也把这个称为带参数的宏。总之,它就像函数一样,可以使用参数。只是它与函数的定义不同,它没有返回值。 类似于内联函数,在编译的第一个阶段,也就是预处理阶段,被完整的替换。

使用cc命令预处理:

gcc -E test.c

我们看到:

int max = MAX(3, 4);

被替换为如下:

int max = 3 > 4 ? 3 : 4;

而且:

#define MAX(a, b) a > b ? a : b

也被删除了,因为一旦被替换,就没有必要在存在了。

1.2.4、定义带可变参数的符号函数

示例:

#include<android/log.h>

#define TAG "com_fpliu_common_httpsign_HttpSign.c"

#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__);
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, TAG, __VA_ARGS__);

int x = 20;
LOGV("x = %s", x)

...表示可变的参数,它只能放在参数列表的最后。

__VA_ARGS__就代表...匹配到的实际参数列表,这里是"x = %s", x

1.2.6、定义符号函数时候使用运算符
1.2.6.1、#运算符

#运算符用于把实际参数字符串化。

示例:

#define PASTE(n) "adhfkj"#n

int main(){
    printf("%s\n", PASTE(15));
    return 0;
}
1.2.6.2、##运算符

##运算符用于把实际参数连接起来。

示例:

#define NUM(a,b,c) a##b##c
#define STR(a,b,c) a##b##c

int main() {
    printf("%d\n", NUM(1, 2, 3));
    printf("%s\n", STR("aa", "bb", "cc"));
    return 0;
}
1.3、Standard C预定义的宏

Standard C规定:在stdlib.h头文件中必须定义下面的宏:

Since类型说明
__DATE__C89char*对源文件进行预处理的日期,格式:Mmm dd yyyy
__TIME__C89char*对源文件进行预处理的时间,格式:hh:mm:ss
__LINE__C89int当前源代码中的行号
__FILE__C89char*当前源代码的文件名
__func__C99char*代表当前函数名称,只能在函数中使用。

这几个宏在调试程序时是很有用的,因为你可以很容易的知道程序运行到了哪个文件的哪一行。

示例:

#include<stdio.h>
#include<stdlib.h>

#define DATE_TIME __DATE__" "__TIME__

void func() {
    printf("release date_time=%s, file=%s, line=%-2d, fuction=%s\n", DATE_TIME, __FILE__, __LINE__, __func__);
}

int main() {
    func();
    printf("release date_time=%s, file=%s, line=%-2d, fuction=%s\n", DATE_TIME, __FILE__, __LINE__, __func__);
    return 0;
}

使用cc命令编译 ⤵︎

gcc -o test test.c

运行结果如下 ⤵︎

release date_time=Mar 13 2020 12:20:52, file=esc.c, line=7 , fuction=func
release date_time=Mar 13 2020 12:20:52, file=esc.c, line=12, fuction=main
1.4、GCC预定义的宏 

Standard C定义的很少,在实际使用中显然是不够的,所以不同的编译器扩展了一些

GCC扩充了的可以通过如下命令获得:

cpp -dM < /dev/null

GCC扩充了非常多的预定义宏,下面只介绍几个常用的:

Since类型说明
__GNUC__intGCC的主版本号
__GNUC_MINOR__intGCC的次版本号
__GNUC_PATCHLEVEL__intGCC的补丁号
__unix__char*代表UNIX系统
__linux__char*代表Linux系统
__FreeBSD__char*代表FreeBSD系统
__NetBSD__char*代表NetBSD系统
_WIN32char*代表32-bit Windows系统
_WIN64char*代表64-bit Windows
_WINDOWSchar*代表GUI程序
_CONSOLEchar*代表控制台程序

使用示例:

#include <stdio.h>

#if defined (__GNUC__) && defined (__GNUC_MINOR__) && defined (__GNUC_PATCHLEVEL__)
#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#endif

int main() {
    /* Test for GCC > 3.2.0 */
    #if defined(GCC_VERSION) && GCC_VERSION > 30200
        printf("GCC_VERSION = %d\n", GCC_VERSION);
    #endif
    return 0;
}

这些特定于编译器的宏,使用的时候需要结合条件编译,以判断他们是否被定义了。

1.5、Visual C++预定义的宏 
Since类型说明
_MSC_VERint如果Visual C++的版本号为15.00.20706.01_MSC_VER宏计算结果为1500
  • 1000 VC++4.0(Visual Studio)
  • 1100 VC++5.0(Visual Studio 97)
  • 1200 VC++6.0(Visual Studio 6.0)
  • 1300 VC++7.0(Visual Studio 2002)
  • 1310 VC++7.1(Visual Studio 2003)
  • 1400 VC++8.0(Visual Studio 2005)
  • 1500 VC++9.0(Visual Studio 2008)
  • 1600 VC++10.0(Visual Studio 2010)
  • 1700 VC++11.0(Visual Studio 2012)
  • 1800 VC++12.0(Visual Studio 2013)
1.6、用于控制流程、逻辑的宏
说明
NDEBUG
如果定义了该宏,assert(CONDITION)宏将不起作用;
如果没有定义该宏,assert(CONDITION)宏才会起作用
常用的./configure --disable-debug就是给编译器传递-DNDEBUG参数。
_FILE_OFFSET_BITS
大多数软件使用这个宏来控制该软件是否提供大文件(大于2G)支持。
-D_FILE_OFFSET_BITS=64表示支持大文件
1.7、__cplusplus宏与extern "C"{}

__cplusplusC++标准定义的。 当编译器C++方式编译代码的时候,会定义此

C++编译器为了支持函数重载, 在汇编的时候,要对函数的名字进行一些处理,加入比如函数的返回类型等等。 而C编译器只是记录了简单的函数名字而已,不会加入其他的信息。 也就是说:编译器C++C的函数的处理是不一样的。

extern "C"{}C++的语法,表示extern "C"{}中的代码按照C语法编译, 这样是为了让C编写的代码尽可能的兼容于C++

所以,我们写C代码的时候,通常会将函数的声明用extern "C"{}包裹起来,以便兼容于C++,好让C++能够调用这些函数。

示例:

#ifndef URL_CODING_H
    #define URL_CODING_H

    #ifdef __cplusplus
        extern "C" {
    #endif

    char* urlEncode(unsigned char *bytes);
    unsigned char* urlDecode(char *url);

    #ifdef __cplusplus
        }
    #endif

#endif

使用cc命令进行预处理:

gcc -E test.c

得到如下:

char* urlEncode(unsigned char *bytes);
unsigned char* urlDecode(char *url);

使用g++命令进行预处理:

g++ -E test.c

得到如下:

extern "C" {
    char* urlEncode(unsigned char *bytes);
    unsigned char* urlDecode(char *url);
}