“#include”一个C程序中的文本文件,作为char

有没有办法在编译时将整个文本文件作为string包含在C程序中?

就像是:

  • file.txt的:

    This is a little text file 
  • main.c中:

     #include <stdio.h> int main(void) { #blackmagicinclude("file.txt", content) /* equiv: char[] content = "This is\na little\ntext file"; */ printf("%s", content); } 

获得一个打印在标准输出的小程序“这是一个小文本文件”

目前,我使用了一个黑客python脚本,但它是丑陋的,只限于一个variables名,你能告诉我另一种方法吗?

我build议使用(unix util) xxd 。 你可以像这样使用它

 $ echo hello world > a $ xxd -ia 

输出:

 unsigned char a[] = { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a }; unsigned int a_len = 12; 

问题是关于C,但是如果有人试图用C ++ 11来完成,那么可以通过新的原始string文字只对包含的文本文件做一点改变就可以完成:

在C ++中这样做:

 const char *s = #include "test.txt" ; 

在文本文件中这样做:

 R"(Line 1 Line 2 Line 3 Line 4 Line 5 Line 6)" 

所以在文件顶部只能有一个前缀,而在它的结尾必须有一个后缀。 在它之间你可以做你想做的事情,只要你不需要特殊的字符序列就不需要特殊的转义)"但是即使你指定了自己的自定义分隔符,

 R"=====(Line 1 Line 2 Line 3 Now you can use "( and )" in the text file, too. Line 5 Line 6)=====" 

你有两种可能性:

  1. 使用编译器/链接器扩展将文件转换为二进制文件,其中正确的符号指向二进制数据的开始和结尾。 看到这个答案: 包括二进制文件与GNU ID链接器脚本 。
  2. 将您的文件转换为可以初始化数组的字符常量序列。 注意你不能只是做“”和跨越多行。 你需要一个连续的字符( \ ),转义字符和其他的字符才能工作,只需要编写一个程序把字节转换成像'\xFF', '\xAB', ...., '\0'这样的序列就可以了。 '\xFF', '\xAB', ...., '\0' (或者使用另一个答案所描述的unix工具xxd ,如果有的话):

码:

 #include <stdio.h> int main() { int c; while((c = fgetc(stdin)) != EOF) { printf("'\\x%X',", (unsigned)c); } printf("'\\0'"); // put terminating zero } 

(未testing)。 然后做:

 char my_file[] = { #include "data.h" }; 

data.h是由哪个生成的

 cat file.bin | ./bin2c > data.h 

好吧,灵感来自Daemin的post,我testing了以下简单的例子:

a.data:

 "this is test\n file\n" 

test.c的:

 int main(void) { char *test = #include "a.data" ; return 0; } 

gcc -E test.c输出:

 # 1 "test.c" # 1 "<built-in>" # 1 "<command line>" # 1 "test.c" int main(void) { char *test = # 1 "a.data" 1 "this is test\n file\n" # 6 "test.c" 2 ; return 0; } 

所以它的工作,但需要用引号包围数据。

如果你做了这样的事情,可能会起作用的是:

 int main() { const char* text = " #include "file.txt" "; printf("%s", text); return 0; } 

当然,你必须小心文件中的实际内容,确保没有双引号,所有适当的字符都被转义了,等等。

因此,如果您只是在运行时从文件加载文本,或将文本直接embedded到代码中,可能会更容易。

如果你还想在另一个文件中的文本,你可以在那里,但它必须在那里表示为一个string。 你会使用上面的代码,但没有双引号。 例如:

 "Something evil\n"\ "this way comes!" int main() { const char* text = #include "file.txt" ; printf("%s", text); return 0; } 

你需要我的xtr实用工具,但你可以用bash script 。 这是一个我称之为bin2inc的脚本。 第一个参数是生成的char[] variable的名称。 第二个参数是file的名称。 输出是C include file ,包含文件内容编码(小写hex )作为给定的variables名称。 char arrayzero terminated ,数据的长度存储在$variableName_length

 #!/bin/bash fileSize () { [ -e "$1" ] && { set -- `ls -l "$1"`; echo $5; } } echo unsigned char $1'[] = {' ./xtr -fhex -p 0x -s ', ' < "$2"; echo '0x00' echo '};'; echo ''; echo unsigned long int ${1}_length = $(fileSize "$2")';' 

你可以在这里获得XTR (字符扩展器)是GPLV3

即使可以在编译时完成(我不认为它可以在一般情况下),文本可能是预处理的头,而不是逐字的文件内容。 我希望你不得不在运行时从文件中加载文本,或做一个讨厌的剪切粘贴作业。

在xh

 "this is a " "buncha text" 

在main.c中

 #include <stdio.h> int main(void) { char *textFileContents = #include "xh" ; printf("%s\n", textFileContents); return 0 } 

应该做这个工作。

Hasturkun使用xxd -i选项的答案非常好。 如果你想把转换过程(文本 – >hex包含文件)直接编译到你的版本中,hexdump.c工具/库最近添加了一个类似于xxd的-i选项的function(它不会给你完整的头文件 – 你需要提供字符数组的定义 – 但它的优点是让你select字符数组的名称):

http://25thandclement.com/~william/projects/hexdump.c.html

它的许可证比xxd更“标准化”,而且非常自由 – 使用它在程序中embeddedinit文件的例子可以在CMakeLists.txt和scheme.c文件中看到:

https://github.com/starseeker/tinyscheme-cmake

将生成的文件包含在源代码树和捆绑实用程序中都有优点和缺点 – 如何处理它将取决于项目的具体目标和需求。 hexdump.c打开这个应用程序的捆绑选项。

我认为单独编译器和预处理器是不可能的。 gcc允许这样做:

 #define _STRGF(x) # x #define STRGF(x) _STRGF(x) printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host " STRGF( # define hostname my_dear_hostname hostname ) "\n" ); 

但不幸的是这不是:

 #define _STRGF(x) # x #define STRGF(x) _STRGF(x) printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host " STRGF( # include "/etc/hostname" ) "\n" ); 

错误是:

 /etc/hostname: In function 'init_module': /etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF" 

为什么不把文本链接到程序中并将其用作全局variables呢! 这是一个例子。 我正在考虑使用它来在可执行文件中包含Open GL着色器文件,因为GL着色器需要在运行时为GPU编译。

我有类似的问题,对于小文件,Johannes Schaub的上述解决scheme对我来说就像一个魅力。

但是,对于大一些的文件,它遇到了编译器的字符数组限制问题。 因此,我编写了一个小型编码器应用程序,将文件内容转换为大小相等的二维字符数组(可能填充零)。 它使用二维数组数据生成输出文本文件,如下所示:

 const char main_js_file_data[8][4]= { {'\x69','\x73','\x20','\0'}, {'\x69','\x73','\x20','\0'}, {'\x61','\x20','\x74','\0'}, {'\x65','\x73','\x74','\0'}, {'\x20','\x66','\x6f','\0'}, {'\x72','\x20','\x79','\0'}, {'\x6f','\x75','\xd','\0'}, {'\xa','\0','\0','\0'}}; 

其中4实际上是编码器中的variablesMAX_CHARS_PER_ARRAY。 带有生成的C代码的文件(例如“main_js_file_data.h”)可以很容易地内联到C ++应用程序中,例如:

 #include "main_js_file_data.h" 

这里是编码器的源代码:

 #include <fstream> #include <iterator> #include <vector> #include <algorithm> #define MAX_CHARS_PER_ARRAY 2048 int main(int argc, char * argv[]) { // three parameters: input filename, output filename, variable name if (argc < 4) { return 1; } // buffer data, packaged into chunks std::vector<char> bufferedData; // open input file, in binary mode { std::ifstream fStr(argv[1], std::ios::binary); if (!fStr.is_open()) { return 1; } bufferedData.assign(std::istreambuf_iterator<char>(fStr), std::istreambuf_iterator<char>() ); } // write output text file, containing a variable declaration, // which will be a fixed-size two-dimensional plain array { std::ofstream fStr(argv[2]); if (!fStr.is_open()) { return 1; } const std::size_t numChunks = std::size_t(std::ceil(double(bufferedData.size()) / (MAX_CHARS_PER_ARRAY - 1))); fStr << "const char " << argv[3] << "[" << numChunks << "]" << "[" << MAX_CHARS_PER_ARRAY << "]= {" << std::endl; std::size_t count = 0; fStr << std::hex; while (count < bufferedData.size()) { std::size_t n = 0; fStr << "{"; for (; n < MAX_CHARS_PER_ARRAY - 1 && count < bufferedData.size(); ++n) { fStr << "'\\x" << int(unsigned char(bufferedData[count++])) << "',"; } // fill missing part to reach fixed chunk size with zero entries for (std::size_t j = 0; j < (MAX_CHARS_PER_ARRAY - 1) - n; ++j) { fStr << "'\\0',"; } fStr << "'\\0'}"; if (count < bufferedData.size()) { fStr << ",\n"; } } fStr << "};\n"; } return 0; } 

你可以使用objcopy来做到这objcopy

 objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o 

现在,您可以将一个目标文件链接到您的可执行文件,该文件包含myfile.txt内容的开始,结束和大小的符号。

我在python3中重新实现了xxd,做了一些修改:

  • Const正确性
  • string长度数据types:int→size_t
  • 空终止(如果你可能需要)
  • Cstring兼容:丢弃unsigned数组。
  • 更小,可读的输出,你会写:可打印ascii是原样输出; 其他字节是hex编码的。

这里是脚本,自己过滤,所以你可以看到它的作用:

pyxxd.c

 #include <stddef.h> extern const char pyxxd[]; extern const size_t pyxxd_len; const char pyxxd[] = "#!/usr/bin/env python3\n" "\n" "import sys\n" "import re\n" "\n" "def is_printable_ascii(byte):\n" " return byte >= ord(' ') and byte <= ord('~')\n" "\n" "def needs_escaping(byte):\n" " return byte == ord('\\\"') or byte == ord('\\\\')\n" "\n" "def stringify_nibble(nibble):\n" " if nibble < 10:\n" " return chr(nibble + ord('0'))\n" " return chr(nibble - 10 + ord('a'))\n" "\n" "def write_byte(of, byte):\n" " if is_printable_ascii(byte):\n" " if needs_escaping(byte):\n" " of.write('\\\\')\n" " of.write(chr(byte))\n" " elif byte == ord('\\n'):\n" " of.write('\\\\n\"\\n\"')\n" " else:\n" " of.write('\\\\x')\n" " of.write(stringify_nibble(byte >> 4))\n" " of.write(stringify_nibble(byte & 0xf))\n" "\n" "def mk_valid_identifier(s):\n" " s = re.sub('^[^_a-z]', '_', s)\n" " s = re.sub('[^_a-z0-9]', '_', s)\n" " return s\n" "\n" "def main():\n" " # `xxd -i` compatibility\n" " if len(sys.argv) != 4 or sys.argv[1] != \"-i\":\n" " print(\"Usage: xxd -i infile outfile\")\n" " exit(2)\n" "\n" " with open(sys.argv[2], \"rb\") as infile:\n" " with open(sys.argv[3], \"w\") as outfile:\n" "\n" " identifier = mk_valid_identifier(sys.argv[2]);\n" " outfile.write('#include <stddef.h>\\n\\n');\n" " outfile.write('extern const char {}[];\\n'.format(identifier));\n" " outfile.write('extern const size_t {}_len;\\n\\n'.format(identifier));\n" " outfile.write('const char {}[] =\\n\"'.format(identifier));\n" "\n" " while True:\n" " byte = infile.read(1)\n" " if byte == b\"\":\n" " break\n" " write_byte(outfile, ord(byte))\n" "\n" " outfile.write('\";\\n\\n');\n" " outfile.write('const size_t {}_len = sizeof({}) - 1;\\n'.format(identifier, identifier));\n" "\n" "if __name__ == '__main__':\n" " main()\n" ""; const size_t pyxxd_len = sizeof(pyxxd) - 1; 

用法(提取脚本):

 #include <stdio.h> extern const char pyxxd[]; extern const size_t pyxxd_len; int main() { fwrite(pyxxd, 1, pyxxd_len, stdout); }