为什么这个.c
文件#include
本身?
vsimple.c
#define USIZE 8
#include "vsimple.c"
#undef USIZE
#define USIZE 16
#include "vsimple.c"
#undef USIZE
#define USIZE 32
#include "vsimple.c"
#undef USIZE
#define USIZE 64
#include "vsimple.c"
#undef USIZE
Run Code Online (Sandbox Code Playgroud)
chq*_*lie 85
该文件包含自身,因此可以使用相同的源代码为宏的特定值生成 4 组不同的函数USIZE
。
这些#include
指令实际上包含在 中#ifndef
,这将递归限制为单个级别:
#ifndef USIZE
// common definitions
...
//
#define VSENC vsenc
#define VSDEC vsdec
#define USIZE 8
#include "vsimple.c"
#undef USIZE
#define USIZE 16
#include "vsimple.c"
#undef USIZE
#define USIZE 32
#include "vsimple.c"
#undef USIZE
#define USIZE 64
#include "vsimple.c"
#undef USIZE
#else // defined(USIZE)
// macro expanded size specific functions using token pasting
...
#define uint_t TEMPLATE3(uint, USIZE, _t)
unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
...
}
unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
...
}
#endif
Run Code Online (Sandbox Code Playgroud)
该模块中定义的函数有
// vsencNN: compress array with n unsigned (NN bits in[n]) values to the buffer out. Return value = end of compressed output buffer out
unsigned char *vsenc8( unsigned char *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsenc16(unsigned short *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsenc32(unsigned *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsenc64(uint64_t *__restrict in, size_t n, unsigned char *__restrict out);
// vsdecNN: decompress buffer into an array of n unsigned values. Return value = end of compressed input buffer in
unsigned char *vsdec8( unsigned char *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsdec16(unsigned char *__restrict in, size_t n, unsigned short *__restrict out);
unsigned char *vsdec32(unsigned char *__restrict in, size_t n, unsigned *__restrict out);
unsigned char *vsdec64(unsigned char *__restrict in, size_t n, uint64_t *__restrict out);
Run Code Online (Sandbox Code Playgroud)
它们都是从vsimple.c中的两个函数定义扩展而来:
unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
...
}
unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
...
}
Run Code Online (Sandbox Code Playgroud)
和宏在conf.hTEMPLATE2
中TEMPLATE3
定义为
#define TEMPLATE2_(_x_, _y_) _x_##_y_
#define TEMPLATE2(_x_, _y_) TEMPLATE2_(_x_,_y_)
#define TEMPLATE3_(_x_,_y_,_z_) _x_##_y_##_z_
#define TEMPLATE3(_x_,_y_,_z_) TEMPLATE3_(_x_, _y_, _z_)
Run Code Online (Sandbox Code Playgroud)
这些宏是经典的预处理器结构,用于通过标记粘贴创建标识符。TEMPLATE2
和TEMPLATE2_
更常被称为GLUE
和XGLUE
。
函数模板开始为:
unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) ...
Run Code Online (Sandbox Code Playgroud)
它在第一个递归包含中被扩展,USIZE
定义为8
:
unsigned char *vsenc8(uint8_t *__restrict in, size_t n, unsigned char *__restrict out) ...
Run Code Online (Sandbox Code Playgroud)
第二个递归包含USIZE
定义为16
,将模板扩展为:
unsigned char *vsenc16(uint16_t *__restrict in, size_t n, unsigned char *__restrict out) ...
Run Code Online (Sandbox Code Playgroud)
还有 2 个包含体定义vsenc32
和vsenc64
。
这种预处理源代码的用法在单独的文件中更为常见:一个用于实例化部分,具有所有通用定义,尤其是宏,另一个文件用于代码和数据模板,该文件多次包含不同的宏定义。
一个很好的例子是从 QuickJS 中的原子和操作码定义生成枚举、字符串和结构数组。
Foo*_*ooF 41
@chqrlie 接受的答案 100% 解释了正在发生的事情。这只是一个补充性的评论。
vsenc8
如果使用 C++,我们可以定义两个模板函数来提供, vsenc16
, vsenc32
,vsenc64
和vsdec8
, vsdec16
, vsdec32
,的所有实现vsdec64
。但相比之下,C 是一种非常简单的语言,并且不支持模板。拥有相同功能(在更丑陋的包装中)的一个常见技巧是使用该语言的哑宏功能,并让 C 预处理器为我们完成同等的工作。大多数具有一定经验的 C 程序员在其职业生涯中都会反复遇到并使用这种构造。
这个特定示例理解起来有点乏味,因为实现文件被非常规地解析了 5 次,首先有一些准备定义,然后是两个函数的四个变体。第一遍(在#ifndef USIZE
预处理器块内)将定义所需的宏和非变量内容,并将使用不同的值(、、、 )作为模板值自行递归#include
四次。当递归包含时,相应的预处理器块将根据用于传递的宏常量的值生成的两个函数的结果进行解析。USIZE
8
16
32
64
#else
USIZE
更传统、概念上更清晰且易于理解的方法是包含来自不同文件的模板函数,例如vsimple.impl
:
#define USIZE 8
/* Generate vsenc8(), vsdec8()... */
#include "vsimple.impl"
#undef USIZE
#define USIZE 16
/* Generate vsenc16(), vsdec16()... */
#include "vsimple.impl"
#undef USIZE
#define USIZE 32
/* Generate vsenc32(), vsdec32()... */
#include "vsimple.impl"
#undef USIZE
#define USIZE 64
/* Generate vsenc64(), vsdec64()... */
#include "vsimple.impl"
Run Code Online (Sandbox Code Playgroud)
然后,还可以将包含文件vsimple.c
和被包含文件vsimple.impl
组织得更加清楚它们定义的内容和时间。大多数 C 程序员都会识别实现模式并立即知道发生了什么。
以这种方式递归地、重复地包含自身会让人产生一种魔术般的感觉,这会为模糊的 C 竞赛条目赢得掌声,但不会为关键任务的生产代码赢得掌声。