c++C语言程序员

C语言编译预处理技术一本道来

2017-12-04  本文已影响30人  PcDack
编译&&预处理.png

一个.C程序,从人懂到计算机懂的流程

编译流程.png

分别简述

预编译(不会去报错,没有真正的到达编译环境)

预处理指令(gcc)

gcc -E file.c -o file.i

判官编译(进行词法和语法分析)

编译指令

gcc -S file.c -o file.s

汇编

汇编指令:

gcc -c file.s -o file.o

链接器的意义

总结


宏定义与使用分析

定义宏常量

宏表达式

容易出错的宏表达式

#define SUM(a,b)( (a)+(b))//不加括号会产生细节错误

void main()

{

    int a=3,b=4;

    int i=SUM(a,b)*SUM(a,b);

}

结果为49

如果我们写成

#include<stdio.h>
#define SUM(a,b) (a)+(b)//不加括号会产生细节错误
void main()
{
    int a=3,b=4;
    int i=SUM(a,b)*SUM(a,b);
    printf("%d\n",i);
}

结果为19
压死程序的最后一个括号

产生错误,我们要分析他的缘由,通过预处理命令得到预处理结果,我们会发现程序变成:

void main()
{
    int a=3,b=4;
    int i=(a)+(b)*(a)+(b);
    printf("%d\n",i);
}

很显然,宏函数只是无脑替换.所以,宏函数虽好,可不要贪用哦

好用的宏表达式

求数组的个数

#define DIM(array)(sizeof(array)/sizeof(*array))

这样一个宏解决函数解决不了的问题

最佳示例

#include<stdio.h>
#define MIN(b,c)((b)<(c)?(b):(c))
int main()
{
    int a=2,b=5;
    printf("%d\n",MIN(a++,b));
    return 0;
}

答案为,我们通过编译预处理,就知道为什么了

最不像C语言的C语言

#include<stdio.h>
#include<malloc.h>
#define MALLOC(type,n) (type*)malloc(sizeof(type)*n)
#define FOREACH(b,e) for(i=b;i<e;i++)
void main()
{
    int i=0;
    int a[]={1,2,3,4,5};
    int *p=MALLOC(int,5);
    FOREACH(0,5)
    {
        p[i]=a[i];
    }
    FOREACH(0,5)
    {
        printf("%d\n",p[i]);
    }
        
}

这个例子主要表达了宏的作用

宏表达式与函数的对比

内置的宏

含义 示例
__FILE__ 被编译的文件名 file1.c
__LINE__ 当前行号 25
__DATE__ 编译的时间日期 Jan 31 2017
__TIME__ 编译时的时间 17:01:01
__STDC__ 标准C

最佳实践

宏日志

#include<stdio.h>
#include<time.h>
#define LOG(s) do                                                          \
{                                                                          \
    time_t t;                                                              \
    struct tm* ti;                                                         \
    time(&t);                                                              \
    ti=localtime(&t);                                                      \
    printf("%s,%s:%d %s\n",asctime(ti),__FILE__,__LINE__,s);               \
}while(0)
void main()
{
    LOG("ENTER the main");
    
}

这个可以直接放在一个头文件里面当做库来用,当然还可以优化加入一些自定义的东西.


条件编译使用分析

if...#else...#endif,,,在编译期之前就已经处理好了

简单示例

#include<stdio.h>
#define D 1
int main()
{
#if(D==1)
    printf("D==1\n");
#else
    printf("D!=1\n");
#endif
}

条件编译的用处

判断头文件中是否有相同的变量

程序1global .h

#ifndef _GLPBAL_H_
#define _GLPBAL_H_
int global = 10;
#endif

程序2test.h

#include <stdio.h>
#include "global.h"

const char* NAME = "Hello world!";

void f()
{
    printf("Hello world!\n");
}

程序3test.c

#include <stdio.h>
#include "global.h"

const char* NAME = "Hello world!";

void f()
{
    printf("Hello world!\n");
}

头文件global.h调用了两次,是不是重复调用呢?很显然,我们通过条件编译技术,防止了重复调用。

条件编译的意义

最佳示例,区分编译产品的调试版和发布版

#include <stdio.h>

#ifdef DEBUG
    #define LOG(s) printf("[%s:%d] %s\n", __FILE__, __LINE__, s)
#else
    #define LOG(s) NULL
#endif

#ifdef HIGH
void f()
{
    printf("This is the high level product!\n");
}
#else
void f()
{
}
#endif

int main()
{
    LOG("Enter main() ...");
    
    f();
    
    printf("1. Query Information.\n");
    printf("2. Record Information.\n");
    printf("3. Delete Information.\n");
    
    #ifdef HIGH
    printf("4. High Level Query.\n");
    printf("5. Mannul Service.\n");
    printf("6. Exit.\n");
    #else
    printf("4. Exit.\n");
    #endif
    
    LOG("Exit main() ...");
    
    return 0;
}

同一份代码我们通过 DEBUG,或者HIGH,LOW来控制,不同的版本.

小结


#include的困惑

当然这一切动作都是在编译预处理之前完成的


#error和#line

# error

#error message
注:message不需要用双引号包围

最佳实例

#include<stdio.h>
int main()
{
    #ifndef COMMAND
    #warning you have not dingYi COMMAND
    #error No COMMAND
    #endif
    printf("%s\n",COMMAND);
} 


#line

用法一


#include<stdio.h>
#line 14 "hello.c"
void f()
{
    return 0;
}
void main()
{
    f();
}

报错信息

ello.c: In function ‘f’:
hello.c:16:9: warning: ‘return’ with a value, in function returning void

这里将line所在的行号改为14行,所以return 0为16行

用法二

我们也可以用line来指定是谁写的

格式

#line 1 "傻帽写的"

#的本质是重定义LINEFILE

/#error编译指示字用于自定义程序员特有的编译错误消息

类似的,#warning用于生成编译警告信息,不会停止编译


#pragma预处理分析

pragma message

最佳实例

#include<stdio.h>

#if defined ANDROID20
    #pragma message("the version is 20..")
    #define VERSION "ANDROID20"
#else
    #pragma message("hehe")
#endif

int main()

{

    printf("%s,\n",VERSION);

    return 0;

}

#pragma pack

最佳演算

演算.png
从图中可以看出,一开始char c1起始位置为0,大小为1。第二个是short2个字节,所以第一个块分配完成,第二个块从c2开始,但是,i的大小为4所以,第二个块剩余部分无法填充,只能开第三个块.三个块的大小就是3*4=12 个字节

如果我们把程序换下位置

#include<stdio.h>
struct S1{
        char c1;
        char s;
        short c2;
        int i;
};
int main(){
        struct S1 s1;
        printf("%d\n",(int)sizeof(struct S1));
        return 0;
}

大小就变成8个字节

最佳示例

#include <stdio.h>
#pragma pack(8)
struct S1
{
    short a;
    long b;
};
struct S2
{
    char c;
    struct S1 d;
    double e;
};
#pragma pack()
int main()
{
    struct S2 s2;
    
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));

    return 0;
}

注意

gcc没有八个字节对齐


#和##运算符使用解析

重要技巧点

#include<stdio.h>
#define CONVERS(x) #x
int main()
{
    printf("%s\n",CONVERS(helloworld!));
    printf("%s\n",CONVERS(100));
    return 0;
}

输出的结果为hello world100

#include<stdio.h>
#define CALL(f,p) (printf("CALL function %s\n",#f),f(p))
int square(int n)
{
    return n*n;
}
int f(int x)
{
    return x;
}
void main()
{
    printf("1.%d\n",CALL(square,4));
    printf("2.%d\n",CALL(f,10));
}

##运算符用于在编译期沾粘两个符号

#include<stdio.h>
#define NAME(n) name##n
int main()
{
    int NAME(1);
    int NAME(2);
    NAME(1)=1;
    NAME(2)=2;
    printf("%d\n",NAME(1));
    printf("%d\n",NAME(2));
    return 0;
}

编译预处理后NAME(1)就变成NAME1,NAME(2)就变成NAME2

最佳用法

利用##定义结构类型

超偷懒

 #include<stdio.h>
#define STRUCT(type) typedef struct _tag_##type type;\
struct _tag_##type

STRUCT(Student)
{
    char * name;
    int score;
};
void main()
{
    Student s1;
    s1.name="hehe";
    s1.score=10;
    printf("%s\n",s1.name);
    printf("%d\n",s1.score);
}

相比

typedef struct Student 
{
    char * name;
    int score;
}Student;

简单好多

上一篇 下一篇

猜你喜欢

热点阅读