预处理器tomfoolery(字符串化#include)

时间:2011-06-28 05:54:37

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

注意:这个问题与OpenCL本身无关...查看最后一段是否有我的问题的简洁陈述。但要提供一些背景知识:

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

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

xxd

请注意,如果可能的话,我不希望在我的OpenCL代码中嵌入#define STRINGIFY(src) #src inline const char* Kernels() { static const char* kernels = STRINGIFY( #include "kernels/util.cl" #include "kernels/basic.cl" ); return kernels; } 宏(如上面提到的SO问题中所做的那样)。现在,这在Clang / LLVM编译器上运行得非常好,但是GCC死了一个可怕的死亡(“未终止的参数列表调用宏STRINGIFY”和与.cl文件的内容相关的各种语法“错误”出现)。所以,显然这个确切的技术在编译器中是不可用的(没有尝试过MSVC,但我也希望它能在那里工作)...我怎样才能最小化地按摩它以便它可以在编译器中工作?

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

编辑:正如Potatoswatter指出的那样,上面的行为是未定义的,所以一个真正的交叉编译器预处理器技术不涉及触摸要被字符串化的文件可能不是'可能(第一个找出 为大多数/所有编译器工作的令人发指的黑客获得答案点的人)。对于好奇的人,我最终做了第二个回复here中建议的内容......也就是说,我将STRINGIFY宏直接添加到我包含的OpenCL文件中:

STRINGIFY

somefile.cl

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

somefile.cpp

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

#define STRINGIFY(src) #src

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

只要STRINGIFY宏在两个inline const char* Kernels() { static const char* kernels = #include "utility_functions.cl" #include "somefile.cl" ; return kernels; } 文件中,字符串就会连接起来,这样就可以模块化你的OpenCL代码。

4 个答案:

答案 0 :(得分:4)

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

  

由最外部匹配括号限定的预处理标记序列形成类似函数的宏的参数列表。列表中的各个参数由逗号预处理标记分​​隔,但匹配内部括号之间的逗号预处理标记不会分隔参数。如果(在参数替换之前)任何参数都不包含预处理标记,则行为是未定义的。如果在参数列表中存在预处理标记序列,否则这些标记将作为预处理指令,则行为未定义。

提取要点:

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

答案 1 :(得分:1)

传统技术使用像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);

我发现这些都不是你要求的预处理器,但它根本就不是为了做到这一点。不过,我很想知道它是否可以。

答案 2 :(得分:0)

你不能这样做;我很惊讶它在clang中工作。如果要直接在二进制文件中包含文本文件,可以选择以下选项:

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

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

最安全的方法是选项1。

答案 3 :(得分:-2)

这是我在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);

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