程序环境和预处理

程序的翻译环境

1.翻译环境:将源代码转为可执行的机器指令。

程序的执行环境

1.执行环境:用于实际执行代码。

详解:C语言程序的编译+链接

1.要了解的名词:源文件 (c),目标文件(obj) 编译器 ,链接器 ,链接库,可执行程序。

2.源文件(可多个) ——> 编译器(每个源文件对应一个)——>目标文件——> 链接器(将目标文件捆在一起) ——>可执行程序。

链接库——> 链接器 ——>可执行程序。

会引入标准C函数库中任何被该程序所用到的函数,它还可以搜索程序员个人的程序库

3.编译(阶段):1.预处理阶段 , 2. 编译 ,3.汇编 ,4.链接。

1.预处理阶段:gcc -E test.c -o test.i   预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件。

2.编译:gcc -S test.i -o test.s  经过语法分析 ,词法分析 ,语义分析 ,符号分析 产生的都放入test.s文件。

3.汇编:gcc -C test.s -o test.o  合并段表 ,符号表的合并和符号表的重定位。完成汇编产生test.o文件。

4.链接:gcc test.o -o test 。零散的目标文件、库整合,解决符号依赖,产出真正能跑的程序。

5.一步到位:gcc test.c -o test

  4.程序执行过程:

  1. 程序载入:依赖环境,有操作系统时由系统完成,独立环境下需手工或通过可执行代码置入只读内存等方式载入内存 。
  2. 执行启动:程序执行后调用main函数 。
  3. 代码执行:用运行时堆栈存局部变量和返回地址,也可用静态内存,其中变量值在程序执行全程保留 。
  4. 程序终止:分正常(main函数正常结束 )和意外终止情况 。

预定义符号介绍

__FILE__       //编译时,会自动替换成当前源文件的名字(带路径) 。比如你写代码的文件叫 test.c,它就变成 "test.c" (具体可能带完整路径,看编译器)。
__LINE__      //替换成当前代码所在的行号 。比如代码写在第 10 行,它就变成 10 ,用来调试、定位代码位置很方便。
__DATE__     //替换成文件编译的日期 ,格式是 "MMM DD YYYY" (比如 "Jun 29 2025" )
__TIME__      //替换成文件编译的时间 ,格式是 "HH:MM:SS" (比如 "14:30:00" )。
__STDC__    //如果编译器遵守 ANSI C 标准(比如常见的 GCC、Clang 基本都满足),它的值就是 1 ;不遵守的话,可能没定义(或者值不是 1 )。用来判断编译器是否符合标准。

 1.解释:这是 C 语言里的预定义符号,是编译器内置的,不用自己定义就能直接用,作用是在编译时获取一些环境信息。

2.例子:

printf("file:%s line:%d\n", __FILE__, __LINE__); 

编译时:

__FILE__ 会被替换成你当前代码文件的名字(比如 " test.c " )。

__LINE__ 会被替换成这行代码所在的行号(比如代码在第 5 行,就变成5 )。

 运行后,输出就会是:file:test.c line:5 (假设文件是 test.c ,代码在第 5 行 ),相当于动态 “打印出代码在哪个文件、哪一行”,调试时能快速定位问题。

预处理指令#define

1. 基本语法

用 #define name stuff 格式,把 name 定义为 stuff ,编译前会做「文本替换」,让代码更灵活、简洁。(一般用大写字母)

2. 常见用法示例

  • 常量定义:如 #define MAX 1000 ,用 MAX 代替数值,方便修改、增强可读性。
  • 简化关键字:如 #define reg register ,给关键字起简短别名,写代码更顺手。
  • 语法简化 / 自定义逻辑
    • #define do_forever for(;;) :用更直观的名字封装逻辑(这里是无限循环)。
    • #define CASE break;case :写 case 时自动加 break ,减少重复代码(利用预处理自动补充逻辑)。
  • 多行定义:若 stuff 过长,除最后一行外,每行末尾加 \(续行符)。

————问题:define定义后面要加;吗?

——回答:最好不要,它就像当于替换的,你加;号,如果你要用这个name ,会一不小心再+;号:会导致程序报错。

3.定义宏函数

// 计算平方的宏(存在隐患)
#define SQUARE(x) x * x

// 改进版:添加括号避免优先级问题
#define SQUARE(x) ((x) * (x))

// 安全的数值计算宏
#define DOUBLE(x) ((x) + (x))

宏和函数的对比

属性 宏定义 函数
代码长度 每次使用时插入代码,可能导致程序膨胀 代码集中,仅在调用时执行
执行速度 无函数调用开销,速度更快 存在调用和返回开销
类型安全 类型无关,可能引发隐式错误 类型严格检查,更安全
副作用处理 参数可能被多次求值,副作用难控 参数仅求值一次,结果可控
调试支持 无法单步调试 支持完整调试
递归能力 不支持递归 支持递归调用

预处理操作符###的介绍

1.#:将参数转换为字符串。

#define PRINT(value) printf("值为: " #value "\n", value)
PRINT(10);  // 输出:值为: 10

2.##:连接两个符号为一个标识符

#define CONCAT(name, num) name##num
int CONCAT(var, 1) = 100;  // 等价于 int var1 = 100;

这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。 
3.副作用:表达式求值时产生的永久性效果,比如 x++(会改变 x 的值,属于有副作用);而 x + 1 不改变 x 原值,无副作用。

若宏参数在宏定义中多次出现,且参数带副作用(如 x++ 这类改变变量值的操作 ),宏展开后会因参数重复执行副作用,导致结果不可预测

4.例子:

#define MAX(a, b) ((a) > (b) ? (a) : (b)) ,当传入带副作用的参数(如 x++y++ ):

z = MAX(x++, y++);
//宏展开后变为
z = ((x++) > (y++)) ? (x++) : (y++);

由于 x++y++ 多次执行,最终 xyz 的结果会因副作用叠加,出现不符合预期的情况(比如示例里最终 x=6y=10z=9 )。 

预处理指令 #include

#include

直接在标准库路径中查找适用于包含系统头文件

#include "filename.h"

先在当前源文件目录查找,再查找标准路径适用于包含自定义头文件

嵌套头文件的解决方案

当多个头文件相互包含时,可能导致内容重复编译,解决方案:

  1. 使用#ifndef/#define/#endif包裹头文件内容
  2. 使用#pragma once指令(现代编译器支持)

预处理指令 #undef

1.解释:C 语言预处理指令,作用是移除(取消定义 )之前用 #define 定义的宏。比如 #undef NAME 就是移除名为 NAME 的宏定义,常用于宏的重新定义场景,重新定义前需先移除旧定义 。

条件编译

条件编译允许根据不同条件选择性编译代码,常用于:                

        1.调试代码的开关控制

        2.跨平台代码适配

        3.版本差异化处理

基础条件编译
#define DEBUG_MODE 1

#if DEBUG_MODE
    printf("调试模式已开启\n");
    // 调试相关代码
#endif
多分支条件编译
#if defined(OS_WINDOWS)
    // Windows 平台代码
#elif defined(OS_LINUX)
    // Linux 平台代码
#else
    // 其他平台代码
#endif
定义检查编译
// 方式一
#ifdef CONFIG_FEATURE_A
    // 启用功能A的代码
#endif

// 方式二
#if defined(CONFIG_FEATURE_B)
    // 启用功能B的代码
#endif
头文件防重复包含
// 传统方式
#ifndef __MY_HEADER_H__
#define __MY_HEADER_H__

// 头文件内容...

#endif  // __MY_HEADER_H__

// 现代方式(部分编译器支持)
#pragma once

常见的条件编译指令 

1. 
#if 常量表达式
 //... 
#endif 
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1 
#if __DEBUG__ 
 //.. 
#endif 
2.多个分支的条件编译
#if 常量表达式
 //... 
#elif 常量表达式
 //... 
#else 
 //... 
#endif 
3.判断是否被定义
#if defined(symbol) 
#ifdef symbol 
#if !defined(symbol) 
#ifndef symbol 
4.嵌套指令
#if defined(OS_UNIX) 
 #ifdef OPTION1 
 unix_version_option1(); 
 #endif 
 #ifdef OPTION2 
 unix_version_option2(); 
 #endif 
#elif defined(OS_MSDOS) 
 #ifdef OPTION2 
 msdos_version_option2()
    #endif
#endif

你可能感兴趣的:(c语言)