C语言 程序环境和预处理

程序的翻译环境和执行环境

在ANSI C的任何一种实现中,存在两个不同的环境。

  • 翻译环境: 在此环境中源代码被转换为可执行的机器指令;
  • 执行环境: 它用于运行程序.

程序的编译

  • 预处理: gcc -E hello.c -o hello.i 本质是文本操作
    • 头文件的插入
    • 宏定义的替换
    • 条件编译的处理
    • 删除注释
  • 编译: gcc -S hello.i -o hello.s 将预处理后的源代码转化成汇编代码
    • 词法分析
    • 语法分析
    • 语义分析
    • 符号汇总
    • ...
  • 汇编: gcc -c hello.s -o hello.o
    • 生成符号表
    • 将汇编代码转换成机器指令(目标文件)
      • 目标文件为 elf 格式, 可使用 readelf -a hello.o 查看
  • 链接: 将多个目标文件和链接库链接在一起生成一个可执行文件 gcc main.o sum.o -o app.out
    • 合并段表
    • 符号表的合并和重定位

预处理

预定义符号

__FILE__        //进行编译的源文件
__LINE__        //文件当前的行号
__DATE__        //文件被编译的日期
__TIME__        //文件被编译的时间
__FUNCTION__    //进行编译的函数
__STDC__        //如果编译器遵循ANSI C,其值为1,否则未定义
#include <stdio.h>

int main() {
    printf("__FILE__: %s\n", __FILE__);
    printf("__LINE__: %d\n", __LINE__);
    printf("__DATE__: %s\n", __DATE__);
    printf("__TIME__: %s\n", __TIME__);
    printf("__FUNCTION__: %s\n", __FUNCTION__);
    printf("__STDC__: %d\n", __STDC__);
    return 0;
}

#define

概述

语法:

  • #define identifier token-string token-string可选
  • #define identifier(parament-list) token-string
    注意:
  1. parament-list 是一个由逗号隔开的符号表, 他们可能出现在 token-string 中.
  2. 参数列表的左括号必须与 identifier 相邻. 如果两者之间有任何空白存在, 参数列表就会被解释为 token-string 的一部分.
#include <stdio.h>
#define SQUARE(X) ((X)*(X))
#define DOUBLE(X) ((X)+(X))
int main() {
    printf("SQUARE(4)=%d\n", SQUARE(4));
    printf("2*DOUBLE(4)=%d\n", 2*DOUBLE(4));
    return 0;
}

#define替换规则

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名,被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define定义中可以出现其它#define定义的常量。但是对于宏,不能出现递归。
  2. 当与处理器搜索#define定义的符号时候,字符串常量的内容并不被搜索。

# 和 ##

#include <stdio.h>
#define PRINT(X) printf("the value of " #X " is %d\n", X)
#define STRCAT(X,Y,Z) X##Y##Z

int main() {
    printf("Hello" "World" "\n");

    int a = 10;
    // print: the value of a is 10
    PRINT(a);

    int class_101 = 250;
    printf("class_101 = %d\n", STRCAT(class, _, 101));
    return 0;
}

带有副作用的宏

#include <stdio.h>
#define MAX(X, Y) ((X)>(Y) ? (X): (Y))

int main() {
    int a = 5, b = 8;
    int m = MAX(a++, b++); // ((a++)>(b++) ? (a++): (b++))
    printf("MAX(a, b) = %d\n", m);
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

#undef

移除一个宏定义
语法 #undef name

#include <stdio.h>
#define N 100
int main() {
    printf("N = %d\n", N);
#undef N
//    printf("N = %d\n", N);
    return 0;
}

命令行定义

新建 cmd.c 文件, 内容为:

#include <stdio.h>

int main() {
    int arr[N] = {0};
    for (int i = 0; i < N; ++i) {
        arr[i] = i;
    }
    for (int i = 0; i < N; ++i) {
        printf("%d ", i);
    }
    printf("\n");
    return 0;
}

输入 gcc -D N=5 cmd.c -o cmd.out(-D N=100 指定N的值), 然后运行得出结果如下所示:

条件编译

示例1

#include <stdio.h>
#define DEFINE

int main() {
    // 如果已定义 DEFINE, 参与下面的编译.
#ifdef DEFINE // 等价于 #if defined(DEFINE)
    printf("DEFINE\n");
#endif

    // 如果未定义 NON_DEFINE, 参与下面的编译.
#ifndef NON_DEFINE // 等价于 #if !defined(NON_DEFINE)
    printf("NON_DEFINE\n");
#endif
    return 0;
}

示例2

#include <stdio.h>
int main() {
#if FLAG == 100
    printf("100\n");
#elif FLAG == 200
    printf("200\n");
#else
    printf("FLAG is %d\n", FLAG);
#endif
    return 0;
}

文件包含

语法:

  • 尖括号形式 #include
    • 使用<>包含的头文件一般会先搜索-I选项后的路径(即用gcc编译时的-I选项),之后就是标准的系统头文件路径。
  • 带引号的形式 #include "XXX"
    • 用""号包含的头文件会首先搜索当前的工作目录,之后的搜索路径才是和<>号包含的头文件所搜索的路径一样的路径。
posted @ 2021-10-08 09:33  ZhengQC  阅读(85)  评论(0)    收藏  举报