【问题标题】:Preprocessor tomfoolery (stringifying a #include)预处理器tomfoolery(字符串化#include)
【发布时间】:2011-09-24 00:51:30
【问题描述】:

注意:这个问题与 OpenCL 本身无关...检查最后一段以获得我的问题的简洁陈述。但提供一些背景:

我正在编写一些使用 OpenCL 的 C++ 代码。我喜欢将我的 OpenCL 内核的源代码保存在它们自己的文件中,以简化编码和维护(而不是将源代码直接作为字符串常量嵌入到相关的 C++ 代码中)。这不可避免地会导致在分发二进制文件时如何将它们加载到 OpenCL 运行时的问题——理想情况下,OpenCL 源代码包含在二进制文件中,因此二进制文件不需要位于特定位置在某个目录结构中知道 OpenCL 源代码在哪里。

我想将 OpenCL 文件作为字符串常量包含在某处,最好不使用额外的构建步骤或外部工具(为了跨编译器/跨平台的易用性......即,不接受 @987654323 @ 之类的)。我以为我偶然发现了一种基于 this 线程中第二个答案的技术,如下所示:

#define STRINGIFY(src) #src

inline const char* Kernels() {
  static const char* kernels = STRINGIFY(
    #include "kernels/util.cl"
    #include "kernels/basic.cl"
  );
  return kernels;
}

请注意,如果可能的话,我不希望在我的 OpenCL 代码中嵌入 STRINGIFY 宏(正如上面提到的 SO 问题中所做的那样)。现在,这在 Clang/LLVM 编译器上运行得非常好,但是 GCC 死得很惨(“未终止的参数列表调用宏 STRINGIFY”和与 .cl 文件内容相关的各种语法“错误”出现)。所以,很明显,这种精确的技术不能跨编译器使用(没有尝试过 MSVC,但我希望它也能在那里工作)......我怎样才能最小化地按摩它以便它可以跨编译器工作?

总之,我想要一种符合标准的技术,可以将文件内容作为 C/C++ 字符串常量包含在内,而无需调用外部工具或使用无关代码污染文件。想法?

编辑:正如 Potatoswatter 指出的那样,上述行为是未定义的,因此不涉及接触要字符串化的文件的真正交叉编译器预处理器技术可能不是t 可能(第一个发现确实对大多数/所有编译器都有效的令人发指的 hack 的人得到了答案)。出于好奇,我最终执行了第二个回复 here 中的建议...也就是说,我将 STRINGIFY 宏直接添加到了我包含的 OpenCL 文件中:

somefile.cl:

STRINGIFY(
  ... // Lots of OpenCL code
)

somefile.cpp:

#define STRINGIFY(src) #src

inline const char* Kernels() {
  static const char* kernels =
    #include "somefile.cl"
    ;
  return kernels;
}

这适用于我尝试过的编译器(Clang 和 GCC,因为它在宏中没有预处理器指令),并且至少在我的上下文中不是太大的负担(即,它不会干扰语法高亮/编辑 OpenCL 文件)。像这样的预处理器方法的一个特点是,由于相邻的字符串被连接起来,你可以写

inline const char* Kernels() {
  static const char* kernels =
    #include "utility_functions.cl"
    #include "somefile.cl"
    ;
  return kernels;
}

只要 STRINGIFY 宏在两个 .cl 文件中,字符串就会连接起来,让您可以模块化您的 OpenCL 代码。

【问题讨论】:

  • 对于连接CL代码,clCreateProgramWithSource接受一个字符串列表。
  • 啊是的...忘记了。虽然我认为上述方法总体上仍然更容易,因为编译 cl 文件的代码非常简单。
  • 为什么不把#include 作为源代码? OpenCL 也会对代码进行预处理,您可以通过 -I 编译器选项指定包含文件夹。使文件加载非常舒适......只需让运行时为您完成!不确定它是否可以处理二进制文件,但我想它会工作相同。

标签: c++ include opencl c-preprocessor stringification


【解决方案1】:

标准中最相关的部分是 §16.3/10:

由最外面的匹配括号界定的预处理标记序列形成了类函数宏的参数列表。列表中的各个参数由逗号预处理标记分​​隔,但匹配内括号之间的逗号预处理标记不分隔参数。如果(在参数替换之前)任何参数不包含预处理标记,则行为未定义。如果在参数列表中存在预处理标记序列,否则它们将充当预处理指令,则行为未定义。

提取关键点:

  • 您需要将头文件括在一对括号中,这样宏就不会认为文件中的每个逗号字符都会引入另一个参数。这些括号也将被字符串化,但应该不难解决。
  • #include 放入参数列表中是官方未定义的行为,因此这将是不可移植的。编译器官方不知道您是否希望生成的字符串为"#include \"kernels/util.cl\""

【讨论】:

  • "如果参数列表中有预处理标记序列,否则将充当预处理指令,则行为未定义"啊... :(
【解决方案2】:

传统的技术是使用像 bin2c 这样的程序,通常是匆忙编写的。另一种方法是使用 GNU binutils 中的 objcopy:

$ objcopy -I binary extensions.cfg -O elf32-little -B i386 --rename-section .data=.rodata extensions.o
$ objdump -x extensions.o

extensions.o:     file format elf32-i386
extensions.o
architecture: i386, flags 0x00000010:
HAS_SYMS
start address 0x00000000

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .rodata       00000447  00000000  00000000  00000034  2**0
                  CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
00000000 l    d  .rodata        00000000 .rodata
00000000 g       .rodata        00000000 _binary_extensions_cfg_start
00000447 g       .rodata        00000000 _binary_extensions_cfg_end
00000447 g       *ABS*  00000000 _binary_extensions_cfg_size

-O 和 -B 标志必须与您编译的目标文件之一的 objdump 输出相匹配,以满足链接器的要求,而节重命名只是为了通知运行时链接器此数据是只读的。注意符号,映射到起始地址、结束地址和数据大小。它们每个都算作地址,因此在 C 中您可以将它们与以下内容一起使用:

extern const char _binary_extensions_cfg_start, _binary_extensions_cfg_end;
extern const char _binary_extensions_cfg_size;
for (const char *p=&_binary_extensions_cfg_start; p<&_binary_extensions_cfg_end; p++)
    do_something(p);
memcpy(somewhere, &_binary_extensions_cfg_start, (intptr_t)&_binary_extensions_cfg_size);

我意识到这些都不是您要求的预处理器,但它根本不是为了做到这一点而设计的。不过,我很想知道它是否可以。

【讨论】:

    【解决方案3】:

    你不能那样做;我很惊讶它在铿锵声中工作。如果你想在二进制文件中直接包含文本文件,你有一些选择:

    1:在编译之前运行的预处理程序,将您的 .cl 文件转换为定义字符串的 .cpp 文件。

    2:通过编译器特定的存储将字符串数据存储在已编译的可执行文件中。这就是 Visual Studio 等工具包含 .rc、.ico 和其他用于构建 Windows 应用程序的文件的方式。当然,如上所述,这些是特定于编译器的。

    最安全的方法是选项 1。

    【讨论】:

    • 正如我所说,我不想添加外部工具/额外的构建步骤。我觉得奇怪的是,如果我将宏直接添加到 #included 的文件中,我上面使用的技术也适用于 GCC,所以接近这个的东西必须是可行的。
    【解决方案4】:

    这是我在 xcode C 中所做的:

    const char oursource[60000];
    const char * oursourceptr = oursource;
    const char * * oursourceptrptr = & oursourceptr;
    
    // in function "readfileintostring":
    
    char *fylnm = "/Developer/projs/myproj/mykernel.cl";
    
    long enby; short pathref;
    
    FSRef dink; FSPathMakeRef( (const UInt8 *) &fylnm, &dink, NULL );
    SInt16 forkRefNum;  HFSUniStr255 dataForkName;  FSGetDataForkName(&dataForkName);
    FSOpenFork( &dink, dataForkName.length, dataForkName.unicode, fsRdPerm, (FSIORefNum *) &pathref );
    
    enby = 100000;  FSRead( pathref, &enby, (void *) oursourceptr );
    
    // .. then later ..
    
    program = clCreateProgramWithSource(context, 1, (const char **) oursourceptrptr, NULL, &err);
    

    ...这不是预处理器巫毒,但它对我有用,我可以在我的 .cl 文件中看到突出显示的语法,我什至可以将我的 .cl 复制到 .c 中,更改一个#define,然后它运行作为 xcode C....

    【讨论】:

    • 除非我遗漏了什么,这是否会在 runtime 读取 .cl 文件...? OP 要求在 编译时 将文件内容作为字符串包含在内的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2019-06-21
    • 1970-01-01
    • 2011-11-15
    • 2016-11-09
    • 2013-09-25
    • 2011-10-22
    • 1970-01-01
    相关资源
    最近更新 更多