杂货边角(5):预编译指令#家族define/pragma

编译器提供的预处理指令主要是以#做为首字符的,如本文的两个主角#define和#pragma。合理的使用预处理指令可使得编写的程序更便于修改(#define MAX_SUM 10000)、平台兼容性(#ifdef WIN32 ... #else...#endif)和调试(#define __DEBUG)。以下的例子css

/*********根据操做系统不一样设置不一样的处理方式,以达到一样的功能效果************/
#ifndef WIN32
//Linux brk system call
static int brk(void* end_data_segment) {
    int ret = 0;
    //brk system call number:45
    //in /usr/include/asm-i386/unistd.h:
    //#define __NR_brk 45
    asm("movl $45, %%eax \n\t"
        "movl %1, %%ebx \n\t"
        "int $0x80 \n\t"
        "movl %%eax, %0 \n\t"
        :"=r"(ret):"m"(end_data_segment) );
}
#endif

#ifdef WIN32
#include <Windows.h>
#endif
/***************用于在程序中启用debug定点输出信息的功能*******************/
#ifdef __DEBUG
#define PRINT_ERROR(...) fprintf(stderr, __VA_ARGS__)
#define MY_ASSERT(value) if(!(value)) { PRINT_ERROR("\"%s\" failed at %s:%d %s \n",\
                                                         #value,__FILE__,__LINE__,__FUNCTION__);}
#define SHOW_LOCA() PRINT_ERROR(" current line_NO = %d \n", __LINE__ );
#else
#define PRINT_ERROR(...) 
#define MY_ASSERT(value) 
#define SHOW_LOCA() 
#endif
/* **若是编译时,输入编译参数/D __DEBUG则会启用带有错误信息输出信息的debug版本,若是 **不定义__DEBUG宏则会编译处正常的release版 */

1. #define宏定义和宏参数形式

关于 #define讲解较好的文章请查考这里。其实#define宏定义的形式仍是蛮简单的,毕竟不是Lisp这种把宏做为语言核心特性的顶级玩法。简单的经过一个代码例子便可以说明地差很少。

#include <stdio.h>
#include <string.h>
#include <climits>

/*#define宏定义分为无参数宏定义和有参数宏定义两种*/
#define A 2       //无参数宏定义,属于简单的字符串替换,格式为 “#define 标识符 字符串”

/*有参数宏定义经常使用来作简单的代数替换 **有参数宏定义最值得讲的即是它的几种宏参数特殊操做符#, ##, #@ **字符串化操做符#,符号链接操做符##,字符化操做符#@ **使用示例:#define makechar(x) #@x ** a = makechar(b); ** -------> a='b' */

#define STRCPY(a,b) strcpy(a##_p, #b) //把第一参数后边加上字符_p,把第二个参数变成字符串

//转换宏,类Lisp宏编译功能,多一层转换宏,这样宏定义就再也不是仅仅的字符替换了
//转换宏是指将全部的宏参数在这一层所有展开
#define _STR(s)  #s //将参数s按照符号名解析成字符串,但并不求值
#define STR(s) _STR(s) //转换宏,两层宏定义,即可以对参数进行求值了
//STR(INT_MAX) -> _STR(0x7fff ffff) -> 0x7fffffff


#define _Conju(a, b) a##*##b
#define Conju(a,b) _Conju(a,b) //转换宏,这样科学计数法便被求值了,而不是停留在字符串上
//Conju(A,A) -> _Conju((2), (2)) -> int(2e2)


int main()
{
    char var1_p[20];
    char var2_p[30];
    strcpy(var1_p, "weapon");
    strcpy(var2_p, "tank");

    STRCPY(var1, var2); //等价strcpy(var1_p, "var2");
    STRCPY(var2, var1); //等价strcpy(var2_p, "var1");

    printf("%s\n", var1_p);//输出var2
    printf("%s\n", var2_p);//输出var1

    printf("%d\n", Conju(A, A)); //输出4
    printf("int max: %s\n", STR(INT_MAX));//INT_MAX定义在climits中,输出2147483647 ~ 2G

    return 0;
}

2. #define配合条件编译完成的健壮性程序实践

1. 提升程序平台兼容性和可移植性
在不少硬件系统中都有本身的自定义数据类型,好比Windows32位操做系统下的 int概念可能和16位低位机的字长彻底不同,而编译器默认的数据类型是 word dword long这一类按照字节数严格划分的修饰符,而随着当前64位系统和32位系统混用的现状,也很容易出现数据类型不匹配致使的移植性问题。因此为了提高程序的可移植性,极可能出现以下的的操做

#ifdef _WIN64
  typedef  __int64  LONG_PTR; 
#else
  typedef  long  LONG_PTR;
#endif

事实上这种经过条件编译来约束数据类型的操做是很常见的html

#ifdef WIN32
 typedef unsigned char boolean;       /* Boolean value type. */
 typedef unsigned long int uint32;    /* Unsigned 32 bit value */
 typedef unsigned short uint16;       /* Unsigned 16 bit value */
 typedef unsigned char uint8;         /* Unsigned 8 bit value */
 typedef signed long int int32;       /* Signed 32 bit value */
 typedef signed short int16;          /* Signed 16 bit value */
 typedef signed char int8;            /* Signed 8 bit value */
#endif

2. 开关调试信息web

#ifdef DEBUG
    printf("程序中的DEBUG错误信息函数所有启动\n");
#endif

3. 防止头文件被重复编译加载svg

#ifndef _LIST_H_
#define _LIST_H_
//无论头文件会不会被多个文件引用,都要加上条件编译开关来避免重复包含。 

#ifdef __cplusplus
extern "C" {
#endif

    pList List_new( void );
    void List_free( pList oList );
    int List_getLength( pList oList );
    int List_put( pList oList, const char* pcKey, const void* pvValue );
    ...
...
#endif

3. #pragma指令

#pragma做为编译器开关指令,实际上是最为复杂的预编译指令,可是目前随着IDE和编译器功能的发展,已经不多再用 #pragma指令了,通常出如今嵌入式开发的状况较多,毕竟要精打细算。这里主要说三个较为常见的 #pragma使用方法,其他地操做请参考编译器的手册。

1 . #pragma message (xxx)
用于在编译窗口中输出信息文本,和printf()函数功能差很少,可是对于源代码信息的控制仍是蛮重要的函数

#ifdef WIN32
#pragma message("WIN32 --macro activated!")
#endif

2 . #pragma once
只要在头文件最开始加入这条指令,就能够保证头文件被编译一次,也是最经常使用的pragma指令。ui

3 . #pragma comment(lib, “libname.lib”)
表示在工程文件添加.lib库文件,这样就不用再ld连接命令中再次添加要使用的库了。spa

$ld  -static -e mini_crt_entry minicrt.a -o test

只须要在可执行文件中添加以下指令便可操作系统

#pragma comment(lib, "minicrt.lib")

其他的pragma指令使用方式能够参考这里.net