解析预处理与编译的过程

C++编译过程详解 1.编译2.汇编3.链接。 阅读详情

预处理与编译 —— 藏在 # 号背后的魔法


1. 一句话总结

预处理是编译前的一轮"文本替换"操作,宏不是函数、#include 不是导入、#if 不是 if——理解这一点,就能避开 90% 的宏相关坑。


2. 知识地图

C 语言预处理与编译
│
├── 1. 预定义符号(编译器自带的"环境变量")
│   ├── __FILE__     当前源文件名(字符串)
│   ├── __LINE__     当前行号(整数)
│   ├── __DATE__     编译日期("Mmm dd yyyy")
│   ├── __TIME__     编译时间("hh:mm:ss")
│   └── __STDC__     是否遵循 ANSI C 标准(1 或未定义)
│
├── 2. #define 宏定义(文本替换的核心)
│   ├── 2.1 对象宏(无参宏)
│   │   └── #define M 10
│   ├── 2.2 函数宏(带参宏)
│   │   ├── #define SQUARE(x) ((x)*(x))
│   │   ├── #define MAX(x,y) ((x)>(y)?(x):(y))
│   │   └── #define Malloc(n,type) (type*)malloc(n*sizeof(type))
│   └── 2.3 宏函数的核心规则
│       ├── 每个参数都要加括号
│       ├── 整个表达式也要加括号
│       └── 原因是"替换"而非"求值"
│
├── 3. 条件编译(控制哪些代码参与编译)
│   ├── #if / #elif / #else / #endif       条件判断
│   ├── #ifdef / #ifndef                   是否已定义
│   ├── #if defined(XXX)                   组合条件(推荐)
│   └── 常见用途
│       ├── 防止头文件重复包含
│       ├── 调试/发布代码切换
│       └── 跨平台兼容
│
├── 4. 特殊操作符
│   ├── #   字符串化(将宏参数变成字符串字面量)
│   └── ##  连接符(将两个 token 拼接成一个)
│
└── 5. #include 文件包含
    ├── #include <xxx.h>   系统头文件,从编译器路径查找
    └── #include "xxx.h"   用户头文件,先从当前目录查找

关系解读: 预处理阶段,编译器按顺序处理 #include(文件展开)、#define(宏替换)、#if(条件过滤),最后才把结果交给真正的编译器。这不是"编译的一部分",而是"编译前的一轮文本加工"。


3. 核心概念深度解析

3.1 宏替换过程 —— 请把它想象成"查找替换"

这是整个预处理中最关键的心智模型。看下面这个例子:

#define SQUARE(x) ((x)*(x))

int ret = SQUARE(a + 1);

替换过程(用 ASCII 追踪):

第 1 步:识别宏调用
┌─────────────────────────────────────────────────────────┐
│  SQUARE(a + 1)                                          │
│  ~~~~~~~                                                │
│  编译器看到 SQUARE,发现它是宏,准备替换                    │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
第 2 步:参数替换(纯文本!不做任何计算!)
┌─────────────────────────────────────────────────────────┐
│  ((x)*(x))                                              │
│    │   │                                                │
│    └─x─┘  将 x 替换为 "a + 1"(注意:是文本 "a + 1",    │
│            不是计算结果 6)                               │
│                          │                               │
│                          ▼                               │
│  ((a + 1)*(a + 1))                                      │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
第 3 步:交给编译器(此时宏已经不存在了)
┌─────────────────────────────────────────────────────────┐
│  int ret = ((a + 1)*(a + 1));                           │
│                                                         │
│  编译器看到的是这个,它完全不知道 SQUARE 曾经存在过         │
│  a=5,所以结果是 (6)*(6) = 36                            │
└─────────────────────────────────────────────────────────┘

如果没加括号,悲剧是这样的:

#define SQUARE_BAD(x) x * x     // 危险写法!

int ret = SQUARE_BAD(a + 1);
替换过程:
  SQUARE_BAD(a + 1)
  → a + 1 * a + 1          ← 看!乘法优先级高于加法!
  → a + (1 * a) + 1
  → 5 + 5 + 1 = 11          ← 你以为会得到 36?实际是 11

核心教训:宏替换是"文本级"的复制粘贴,参数在替换时不求值,所以括号就是你的护身符。


3.2 宏函数 vs 普通函数 —— 用表格说话

同样实现"求平方",宏和函数有什么区别?

                    宏(SQUARE_MACRO)              函数(square_func)
                    ──────────────────              ──────────────────
调用时机:          SQUARE(a+1)                     square(a+1)
                    │                              │
                    ▼                              ▼
预处理阶段:         展开为 ((a+1)*(a+1))              什么都没发生
                    │                              │
                    ▼                              ▼
编译阶段:          直接编译乘法表达式                  编译成函数调用指令
                    │                              │
                    ▼                              ▼
运行阶段:          直接计算                          push 参数 → call → 函数体 → ret
                    │                              │
                    ▼                              ▼
结果:              ((a+1)*(a+1)) = 36              6*6 = 36
对比维度宏函数普通函数
处理阶段预处理(文本替换)编译+运行(调用)
参数求值不计算,直接文本替换先计算参数值,再传参
参数类型无类型,任何能参与运算的 token 都行严格类型要求
代码膨胀每次调用展开一次,代码变大一份代码,多次调用
调试无法打断点,出错信息指向展开后的代码可以正常断点调试
副作用参数如果有副作用(如 x++),会被执行多次参数只求值一次
速度无函数调用开销,更快有 call/ret 开销
类型安全无类型检查有类型检查

我的理解: 宏函数本质上是一个"代码生成器"——它帮你在调用处自动复制粘贴一段代码模板,而不是"执行"一段逻辑。所以它快,但容易出错。在生产级代码中,能用 inline 函数替代宏的地方,尽量用 inline。


3.3 条件编译 —— 编译器里的"开关"

条件编译让同一份源码在不同场景下生成不同的程序。这在调试、跨平台、功能裁剪中非常常见。

                    源代码
                      │
                      ▼
         ┌──────────────────────────┐
         │   #if DEBUG              │
         │     printf("调试信息");   │  ← 条件编译块
         │   #else                  │
         │     // 什么都不输出       │
         │   #endif                 │
         └──────────────────────────┘
                      │
             预处理阶段判断
                      │
         ┌────────────┴────────────┐
         │                         │
    DEBUG=1 时                  DEBUG=0 时
         │                         │
         ▼                         ▼
   ┌──────────────┐          ┌──────────────┐
   │ printf(...)  │          │  // 空       │
   └──────────────┘          └──────────────┘
         │                         │
         └────────────┬────────────┘
                      ▼
                交给编译器

常见用法一览:

// 1. 防止头文件重复包含(每个 .h 文件的标准写法)
#ifndef MY_HEADER_H
#define MY_HEADER_H
// ... 头文件内容 ...
#endif

// 2. 调试开关
#ifdef DEBUG
    #define LOG(msg) printf("[DEBUG] %s:%d: %s\n", __FILE__, __LINE__, msg)
#else
    #define LOG(msg)  // 发布版本直接变成空
#endif

// 3. 跨平台区分
#if defined(_WIN32)
    #define PATH_SEP '\\'
#elif defined(__linux__) || defined(__APPLE__)
    #define PATH_SEP '/'
#endif

// 4. 用 #if 0 快速注释整块代码(比 /* */ 更安全,可以嵌套)
#if 0
    // 这段代码完全不参与编译
    int old_function() { ... }
#endif

#if 0 比 /* */ 强在哪? 块注释 /* */ 不能嵌套,如果你要注释掉的代码里本身就含有 /* */,就会提前结束。而 #if 0 ... #endif 可以任意嵌套,是批量禁用代码的最佳实践。


4. 代码实战

以下代码演示了预定义符号、宏函数、带参数的宏、条件编译的综合运用。

#include <stdio.h>
#include <stdlib.h>   // 为 malloc 添加

// ============================================================
// 示例 1:预定义符号 —— 编译器的"身份证"
// ============================================================
void demo_predefined_symbols()
{
    printf("当前文件  : %s\n", __FILE__);   // 输出完整路径
    printf("编译日期  : %s\n", __DATE__);   // 格式 "Jul 31 2026"
    printf("编译时间  : %s\n", __TIME__);   // 格式 "14:30:00"
    printf("当前行号  : %d\n", __LINE__);  // 就是这一行的行号

    // __STDC__ 在符合 ANSI C 标准的编译器上值为 1
    // 某些老编译器或非标准模式下可能未定义
#ifdef __STDC__
    printf("遵循 ANSI C: 是\n");
#else
    printf("遵循 ANSI C: 否\n");
#endif
}

// ============================================================
// 示例 2:调试宏 —— 快速定位问题
// ============================================================
// 注意:宏定义中换行需要用反斜杠 \
#define DEBUG_PRINT(fmt, ...) \
    printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

// 使用示例:
// DEBUG_PRINT("a = %d, b = %d", a, b);
// 输出:[main.c:42] a = 5, b = 3

// ============================================================
// 示例 3:宏函数 —— 括号!括号!括号!
// ============================================================
#define SQUARE(x) ((x)*(x))   // 正确:里外都加括号

void demo_square()
{
    int a = 5;
    int ret = SQUARE(a + 1);    // 展开为 ((a+1)*(a+1)) = 36
    printf("SQUARE(%d+1) = %d\n", a, ret);
}

// ============================================================
// 示例 4:求最大值宏
// ============================================================
#define MAX(x, y) ((x) > (y) ? (x) : (y))

void demo_max()
{
    int a = 3, b = 5;
    int m = MAX(a, b);
    printf("MAX(%d, %d) = %d\n", a, b, m);  // 5
}

// ============================================================
// 示例 5:Malloc 宏 —— 封装 malloc 的繁琐写法
// ============================================================
#define Malloc(n, type)  ((type*)malloc((n) * sizeof(type)))

void demo_malloc()
{
    // 原生写法:需要手动计算大小、手动强转
    // int *pf = (int *)malloc(10 * sizeof(int));

    // 宏写法:意图更清晰
    int *pf = Malloc(10, int);
    if (pf == NULL)
    {
        perror("Malloc");
        return;
    }
    // 使用 pf ...
    free(pf);
}

// ============================================================
// 示例 6:条件编译 —— 调试/发布两套代码
// ============================================================
// 编译时加 -DDEBUG 开启调试,不加则是发布版本

#ifdef DEBUG
    #define LOG(msg) printf("[LOG] %s\n", msg)
#else
    #define LOG(msg)   // 空替换,零开销
#endif

void demo_conditional_compile()
{
    LOG("这条日志只在 DEBUG 模式下出现");
}

// ============================================================
// 示例 7:# 和 ## 操作符
// ============================================================

// # 操作符:将宏参数变成字符串字面量
#define TO_STRING(x) #x
// TO_STRING(hello)  →  "hello"
// TO_STRING(123)    →  "123"

// ## 操作符:将两个 token 拼接成一个
#define CONCAT(a, b) a##b
// CONCAT(my_, var)  →  my_var
// 可以用于自动生成变量名

// 实战:自动生成 getter 函数名
#define MAKE_GETTER(type, name) \
    type get_##name() { return name; }

void demo_hash_operators()
{
    printf("TO_STRING(hello) = %s\n", TO_STRING(hello));
    // 输出:TO_STRING(hello) = hello

    int my_var = 100;
    printf("CONCAT(my_, var) = %d\n", CONCAT(my_, var));
    // 输出:CONCAT(my_, var) = 100
}

// ============================================================
// 示例 8:头文件包含的查找规则
// ============================================================
// #include <stdio.h>      系统头文件,从编译器安装路径查找
//                        (如 /usr/include, /usr/local/include)
//
// #include "myheader.h"   用户头文件,先从当前 .c 文件所在目录查找
//                        找不到再回退到系统路径查找

// ============================================================
// 主函数
// ============================================================
int main()
{
    demo_predefined_symbols();
    demo_square();
    demo_max();
    demo_malloc();
    demo_conditional_compile();
    demo_hash_operators();
    return 0;
}

5. 注释提炼

5.1 知识注解类 —— 扩展科普

原文注释:

在定义宏函数时,在表达式部分要给每一个部分都给上括号,这样才保证不会出现问题
因为宏在预处理时,是进行替换的,而不是像函数一样

扩展解读:

这条注释点出了宏编程的第一铁律。深入分析一下"为什么":

#define SQUARE(x) ((x)*(x))

三个括号各有分工:

括号位置作用去掉后的问题
(x) 中的括号保护参数 x 本身如果 x 是 a+b,不带括号会因运算符优先级出错
外层的 (...)保护整个表达式如果用在 SQUARE(a)/SQUARE(b) 这种上下文中,不带括号也可能出错

一个更极端的例子:

#define DOUBLE(x) (x) + (x)     // 注意:外层没加括号!

int result = 3 * DOUBLE(4);     // 你以为 (3*8)=24
// 展开后:3 * (4) + (4) = 12 + 4 = 16   ← 错了!

正确写法:

#define DOUBLE(x) ((x) + (x))   // 外层也必须加括号

5.2 个人理解类 —— 重新表达

原文注释:

在定义宏函数时,在表达式部分要给每一个部分都给上括号,这样才保证不会出现问题
因为宏在预处理时,是进行替换的,而不是像函数一样

我的理解: 宏函数和普通函数的本质区别在于"求值时机"。函数调用时,参数先被计算成值,再把值传入函数体;宏调用时,参数作为"源代码文本"直接粘贴进宏体,然后才被编译器解析。这意味着宏对参数的内容一无所知——它不知道 x 是单个变量还是 a+b+c 这种复杂表达式。所以唯一的防御手段就是:把所有可能出现优先级问题的地方都用括号包起来。记住一句话:宁可多写括号,不要相信宏的参数。


5.3 踩坑感悟 —— 单独警示

⚠️ 踩坑 1:宏函数中的副作用

#define SQUARE(x) ((x)*(x))

int a = 5;
int ret = SQUARE(++a);   // 展开为 ((++a)*(++a))
                         // a 被自增了两次!结果不可预测!

教训: 永远不要给宏函数传带副作用的表达式(++、--、函数调用等)。宏不关心你传的是什么,它只是做文本替换。++a 作为参数,展开后会被执行两次。这是宏函数最隐蔽的坑,编译器不会警告你。

⚠️ 踩坑 2:宏定义中的多余空格

#define SQUARE (x) ((x)*(x))   // 注意 SQUARE 和 (x) 之间有空格!

这定义的不是带参宏,而是一个无参宏 SQUARE,替换为 (x) ((x)*(x))。因为 #define 的语法是:宏名后面紧跟 ( 才表示带参宏,中间有空格就变成了对象宏。这个错误非常隐蔽,编译器不会报错,但你的代码行为完全不对。

⚠️ 踩坑 3:宏定义末尾的分号

#define MAX(x,y) ((x)>(y)?(x):(y));   // 末尾多了分号!

int m = MAX(a, b);   // 展开后:int m = ((a)>(b)?(a):(b));;  两个分号!
if (m > 10)
    MAX(c, d);       // 展开后:((c)>(d)?(c):(d));;  —— if 后面跟了两条语句!
else                 // 编译错误:else 没有匹配的 if
    ...

教训: 宏定义末尾绝对不要加分号。如果确实需要分号,让调用方来加。更安全的做法是用 do { ... } while(0) 包裹多语句宏。


5.4 对比总结类 —— 可视化增强

#include <xxx.h> vs #include "xxx.h"

特性<xxx.h>"xxx.h"
查找顺序只查系统路径先查当前目录,再查系统路径
典型用途标准库头文件自定义头文件
示例<stdio.h>, <stdlib.h>"myutils.h", "config.h"
查找路径/usr/include, /usr/local/include 等./ → /usr/include → …

#if vs if 语句

特性#ifif
处理阶段预处理时运行时
判断对象宏常量、宏表达式变量、表达式
结果决定代码是否参与编译决定代码是否执行
性能零运行时开销有分支判断开销
能判断变量吗不能(变量在预处理时还不存在)能
典型场景条件编译、版本控制业务逻辑分支

6. 代码修正

修正 1:原代码中 Malloc 宏缺少外层括号

修改前:

#define Malloc(n,type)  (type*)malloc(n*sizeof(type))

修改后:

#define Malloc(n, type)  ((type*)malloc((n) * sizeof(type)))

修改说明:

  1. 参数 n 加了括号 (n),防止 n 是表达式时出错(如 Malloc(a+b, int))。
  2. 整个 malloc 调用加了外层括号,防止在复杂表达式中出现优先级问题。
  3. 乘法运算符 * 两侧加了空格,增强可读性。

修正 2:原代码中 DEBUG_PRINT 宏缺少 __VA_ARGS__ 支持

修改前:

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                         date:%s\ttime:%s\n" ,\
                         __FILE__,__LINE__ ,       \
                         __DATE__,__TIME__ )

问题: 这个宏只能输出固定的文件名、行号、日期、时间,不能输出用户自定义信息(如变量值),实用性有限。

修改后:

#define DEBUG_PRINT(fmt, ...) \
    printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

修改说明:

  • ... 和 __VA_ARGS__ 是 C99 的可变参数宏语法,让宏像 printf 一样接受可变参数。
  • ##__VA_ARGS__ 中的 ## 是 GNU 扩展:当可变参数为空时,它会自动删除前面的逗号,避免语法错误。这是可移植性上的一个权衡——如果只写标准 C,就不能用 ##,但功能上会受限。
  • 使用示例:
    DEBUG_PRINT("a = %d, b = %d", a, b);
    // 输出:[main.c:42] a = 5, b = 3
    

修正 3:原代码中 main 函数重复定义

问题: 原代码通过 #if 0 注释掉了多个 main 函数,但实际开发中,应把不同功能拆成独立函数,由主函数统一调用(如代码实战部分所示),这样更清晰也方便测试。

说明: 虽然 #if 0 是合法的"注释"手段,但正确的做法是每个功能独立成函数,通过条件编译或参数控制调用,而不是写多个 main 然后注释掉。这属于工程习惯问题,不影响编译,但影响代码维护性。


7. 小结

预处理是 C 语言的"编译前预处理车间"——#define 做文本替换、#include 做文件拼接、#if 做代码过滤。掌握宏的括号规则、理解条件编译的应用场景、记住预定义符号的用途,这三个知识点就能覆盖日常开发中 95% 的预处理操作。一句话:宏是文本替换,不是函数调用,写宏时脑子里要时刻画出"展开后的代码"是什么样子。

r语言svr模型_R语言初探强化学习中的马尔可夫模型 强化学习大家这几年应该不陌生,从AlphaGo到AlphaZero让大家见识到了强化学习的力量。我们今天给大家介绍一个在强化学习中核心思维马尔可夫决策过程(MDP)。马尔科夫决策过程是基于马尔科夫论的随机动态系统的最优决策过程。它是马尔科夫过程与确定性的动态规划相结合的产物,故又称马尔科夫型随机动态规划,属于运筹学中数学规划的一个分支。今天我们给大家介绍下马尔可夫决策过程中用到一些算法以... 阅读详情

相关推荐

i.MX RT1064引脚配置与BGA封装选型实战指南

在嵌入式硬件设计中,引脚配置与PCB布局是连接芯片手册与物理实现的关键桥梁,直接影响系统的稳定性与信号完整性。其核心原理在于理解处理器的IOMUX(输入/输出多路复用器)系统与多电源域架构,前者通过软件配置实现引脚功能的灵活复用,后者则为不同外设接口提供独立的电压基准,是保障电源稳定性的基石。从技术价值看,合理的引脚规划能规避信号干扰、电源不稳等潜在风险,提升设计一次成功率。在应用场景上,无论是连接SDRAM、USB、SDIO等高速外设,还是配置UART、I2C等中低速接口,都需要遵循特定的布局布线规则。本

weixin_33217202的博客 314

深入解析C/C++编译链接全过程:从预处理到链接的完整指南

编译与链接是软件开发中的基础概念,指将高级语言源代码转换为计算机可执行程序的过程。其核心原理在于通过多阶段处理实现代码转换与模块整合:预处理阶段展开宏和头文件,编译阶段进行语法语义分析并生成中间代码,汇编阶段将代码转换为机器指令,链接阶段则解决符号引用并合并目标文件。这一过程的技术价值在于实现了代码的模块化与高效构建,使得大型项目可以分而治之,通过增量编译显著提升开发效率。在实际应用场景中,理解编译链接机制能帮助开发者解决常见的“undefined reference”和“multiple definiti

weixin_31742871的博客 356

论文阅读笔记(一)细粒度图像检索

0、参考文献 [1]见微知著 http://geek.youkuaiyun.com/news/detail/191718 [2]Xiu-Shen Wei 《Selective Convolutional Descriptor Aggregation for Fine-Grained Image Retrieval》 [3]Lingxi Xie 《Fine-Grained Image Search》 1...

huang2818138的博客 1万+

C# 工程使用Rider设置所谓的“宏编译”(编译开关)

首先声明一点,C#没有宏定义,只有编译开关 Unity里面的#if UNITY_ANDROID这种的代码都是编译开关,表示在这个情况下才编译。 Unity里面可以直接在ProjectSettings里面设置宏定义变量 c#工程想设置编译开关,本质上需要修改工程的.csproj文件(不同编辑器可能有不同的操作,但本质都是修改这个文件),具体的修改位置如下。 Rider里面修改可以直接右击左侧最外层的工程图标,选择properties 选中configurations,选择一个运行环境(比如我这里选的

qq_37421018的博客 5265

IC618新手避坑指南:从零在Linux上配置Cadence Virtuoso环境(含cds.lib文件详解)

本文详细介绍了在Linux系统上配置Cadence Virtuoso IC618环境的完整指南,特别针对新手常见问题提供解决方案。重点解析了cds.lib文件的关键配置,包括库路径定义和工艺库管理,并分享了高效工作流设置和典型问题排查技巧,帮助工程师快速搭建稳定的EDA设计环境。

weixin_26805597的博客 553

Android Display Graphics #1 整体框架介绍一

手机屏上显示的内容,2D或3D ,如果是 3D 的,不管是 App 还是游戏,或者简单的图片界面,底层都是通过 GPU、 通过 OpenGL(ES)绘制出来的。是Java层的一个类,提供了一系列API,使用这些API可以实现各种的产品经理要求的效果。显示的不同阶段对这块内存的叫法不同,在上层可以是Window、ANnativeWindow、Surface、SurfaceView、layer、buffer,这些本质上没啥区别,可以广泛意义上理解为用于显示内容的那块内存,只不过各个阶段的操作接口不同。

oDwyane03的博客 1387

C# 宏--释义及实例

C# 宏-释义及实例

Blue 2298

信号处理中的滤波器的阶数和谐波的理解

最近在读paper的时候,遇到了一些以前遗忘的问题,先重新查找,记录在这里,以备不时之查看。 我相信一定有小伙伴跟我有过一样的疑惑,那就是我们在使用滤波器的时候,总是遇到滤波器的阶数这个概念参数,我们很多都是根据别的paper里的设置,设置自己的科研参数,其实并不是很理解其中的含义: 其实我们常用的阶数N的意思是: 滤波器的阶数,就是指过滤谐波的次数,一般来讲,同样的滤波器,其阶

Miracle.Zhao的博客 2万+

c#利用宏定义调试代码

#define 使用#define来定义符号。将符号用作传递给#if指令的表达式时,该表达式的计算结果为true,如以下示例所示: #define DEBUG 说明 #define指令不能用于声明常量值,常量定义例:public const int value = 213; C# 中的常量最好定义为类或结构的静态成员。如果具有多个此类常量,请考虑创建一个单独的“常量”...

u011555996的博客 3817

qq邮箱发错误:The “From“ header is missing or invalid. Please follow RFC5322, RFC2047, RFC822 standard pro

如果还报错 (550, b’The mail may contain inappropriate words or content.'),可能是你message[‘From’]中的< senderxxx@qq.com >发送邮箱填错了,必须要和self.sender = ’ senderxxx@qq.com '一样。可能是之前qq邮箱发送协议存在漏洞,可隐藏发送者邮箱。现在换了新协议,导致与原来协议规则、编码出现了一些问题。"python自动发送""python自动发送"

云霄IT的博客 3670

AI Agent在智能广告投放中的应用

随着互联网的快速发展,广告投放市场变得越来越复杂和竞争激烈。传统的广告投放方式往往难以精准地触达目标受众,导致广告资源的浪费。AI Agent作为一种智能的自动化程序,能够在复杂的环境中自主学习、决策和执行任务。本文章的目的在于深入探讨AI Agent在智能广告投放中的应用,研究如何利用AI Agent提高广告投放的精准度、效率和效果,降低成本。范围涵盖了AI Agent的基本原理、相关算法、在广告投放中的具体应用场景以及实际案例分析等方面。

欢迎来到我的优快云空间!这里聚焦AI大模型应用实战,分享前沿技术、实战案例与开发经验。 617

C#.net通过宏条件编译dll

1.项目【属性】【生成】【条件编译符号】输入FOREST,这样就定义了宏FOREST 2.使用宏 #if FOREST //do something #endif

zouxin_88的专栏 682

安装openclaw时出现npm error code ENOENT npm error syscall spawn git报错的解决方案

本文主要介绍了安装openclaw时出现npm error code ENOENT npm error syscall spawn git报错的解决方案,希望能对使用openclaw的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 12万+

【C语言15】预处理

预处理

亿点点细节的博客 234

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

C语言编译全过程:预处理、编译、汇编与链接深入解析

当我们编写高级语言程序时,计算机并不能直接执行源码,中间需要经过一系列转换。编译原理是程序员理解程序构建的基础,而C语言作为最贴近硬件的语言之一,其编译过程尤为典型。一次完整的C语言编译通常被划分为预处理、编译、汇编、链接四个阶段:预处理负责头文件展开与宏替换,编译将C代码翻译为汇编语言,汇编把汇编指令转换为机器码生成目标文件,链接则将多个目标文件与库合并为可执行程序。理解这些阶段不仅能帮助我们快速定位编译错误,还能在性能优化、嵌入式开发、多文件项目管理等场景中游刃有余。通过GCC命令手动验证每一步中间产物

weixin_33508411的博客 155

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

GCC编译全过程解析:从预处理到链接的完整流程

预处理、编译、汇编、链接四阶段 toolchain 的拆解,让我第一次把“写下的.c”与“跑起来的 ELF”之间的黑盒打开:宏在预处理期做纯文本替换,因此必须加括号避免副作用;链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。<> ---表示找包含的文件,到系统默认的路径寻找,系统默认的头文件一般用<>。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。

kkkkkingw的博客 808

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

【C语言】从预处理到机器代码的C语言编译过程解析

使用`#define`指令可以创建宏定义,用于表示一个常量、一个代码片段或一个函数。预处理器会根据宏定义的内容,在代码中进行替换。

Goforyouqp的博客 1万+

Keil编译器的幕后:解析.h与.c文件的预处理、编译与链接全过程

本文深入解析Keil编译器处理.h与.c文件的完整流程,涵盖预编译阶段的宏展开与头文件处理、编译阶段的代码转换与extern关键字应用,以及链接阶段的符号解析。重点探讨变量定义的最佳实践,帮助嵌入式开发者避免常见错误,提升代码质量与编译效率。

garlic的博客 663

深度解析 C 语言编译流程:预处理、编译、汇编、链接的 4 步完整过程

预处理:文本替换 → 生成.i编译:高级语言 → 汇编 → 生成.s汇编:汇编 → 机器码 → 生成.o链接:合并目标文件 + 解析符号 → 生成可执行文件数学表达设源文件为 $S$,编译流程可形式化为:$$ S \xrightarrow{\text{预处理}} I \xrightarrow{\text{编译}} A \xrightarrow{\text{汇编}} O \xrightarrow{\text{链接}} E $$其中 $E$ 为可执行文件,$I, A, O$ 为中间文件。

2501_93892617的博客 619

深入解析GCC编译链接全过程:从预处理到可执行文件的完整指南

在C/C++开发中,编译与链接是将源代码转换为可执行程序的核心过程。其基本原理涉及将人类可读的高级语言代码,通过编译器转换为机器可执行的二进制指令。这一过程的技术价值在于,它不仅是程序构建的基础,更是进行性能优化、问题调试和构建可靠软件系统的关键。理解编译链接的机制,能帮助开发者有效解决“未定义引用”等常见错误,并优化程序的启动速度和文件体积。在实际应用场景中,无论是嵌入式开发、系统编程还是高性能计算,掌握从预处理、编译、汇编到链接的完整流程都至关重要。本文以GCC工具链为例,详细剖析了符号解析、静态库与动

weixin_30800987的博客 312

gcc/g++使用格式+各种选项,程序编译过程 -- 预处理/编译(分析树,符号解析,函数名修饰,编译优化,生成目标代码)/汇编/链接过程,函数库(动态链接过程)

gcc/g++使用格式+各种选项,程序编译过程 -- 预处理/编译(分析树,符号解析,函数名修饰,编译优化,生成目标代码)/汇编/链接过程,函数库(动态链接过程)

m0_74206992的博客 622

C语言的编译链接过程

当我们用C语言编写一个程序时,代码首先以文本形式存储在源文件(如.c文件)中。然而,计算机无法直接执行这种文本形式的代码,需要将其转换成机器语言才能运行。这个转换过程就涉及到编译和链接两个主要阶段。通过以上的介绍和示例,我们对C语言的编译链接过程有了一个较为深入的了解。编译过程将源代码逐步转换成目标文件,而链接过程则将这些目标文件和库文件整合在一起,生成可执行文件。理解这个过程有助于我们更好地编写和调试程序,也能让我们更清楚地了解程序在计算机上的运行机制。希望这篇博客对大家学习C语言有所帮助!

z939188380的博客 309

现代编译过程

现代编译过程编译过程分为五步: 扫描解析文件 预处理工作 处理宏定义,不管是由-D参数指定,还是在源码内部通过#define,或者使用了标准库,扩展库中的宏,都会替换为定义的值。 命令:cpp a.c>a.i 输出的a.i就是预处理过的文件,包含了完整的内容。 由此可以得出,宏不是运行时的定义内容,也不是编译时的内容,而是预处理阶段就完成的。 编译阶段: 将源代码(也就是预处理过的代码)编译

蔡曜镫专栏 550

C语言编译预处理

C语言由源代码生成可执行程序的过程如下: C源程序->编译预处理->编译->优化程序->汇编程序->链接程序->可执行文件 其中编译预处理阶段,读取C源程序,对其中的预处理指令(以#开头的指令)和特殊符号进行处理。或者说是扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。 ...

C语言技术网-码农有道 3321

编译、链接的具体过程——C程序底层运行机制的研究

本文系统介绍了C程序从源代码到可执行文件的完整构建过程,重点解析了编译与链接两个核心阶段。编译过程包括词法分析、语法分析、语义分析、中间代码生成与优化等步骤,将预处理后的代码转换为汇编语言;链接过程则通过符号解析、重定位等操作,将多个目标文件与库文件合并生成最终可执行文件。文章通过具体示例详细说明了每个阶段的处理逻辑,揭示了程序底层运行的机制原理,帮助读者深入理解计算机系统如何将人类可读的代码转化为机器可执行的二进制指令。

Lucis__的博客 1096

了解C语言程序的编译和链接过程

(1)预处理(Preprocessing):预处理阶段通过处理包含在源代码中的预处理指令(以"#"开头),如宏定义、头文件包含等,生成经过预处理的源代码。(2)重定位(Relocation):重定位是将各个目标文件中的代码和数据段按照其在内存中的实际位置进行组织和调整,以便正确地映射到可执行文件中。(1)符号解析(Symbol resolution):在链接过程中,编译器会根据符号表来解析目标文件中的符号引用,寻找其对应的定义。(1)编译器:编译器负责将源代码转换为目标文件,包括预处理、编译和汇编等步骤。

m0_70865803的博客 240
上一篇: 万字解析动态内存从编译到运行
下一篇: 数据结构——队列的概念解析与核心代码
DDXYcoder
博客等级 码龄1年 140粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值