"#include " текстовый файл в программе C в виде символа[]

есть ли способ включить весь текстовый файл в виде строки в программу C во время компиляции?

что-то типа:

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

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

получение небольшой программы, которая печатает на stdout " это немного текстовый файл"

на данный момент я использовал хакерский скрипт python, но он уродливый и ограничен только одним именем переменной, можете ли вы сказать мне другой способ сделать это?

15 ответов


Я бы предложил использовать (unix util)xxd в для этого. вы можете использовать его так

$ echo hello world > a
$ xxd -i a

выходы:

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

вопрос был о C, но в случае, если кто-то попытается сделать это с C++11, то это можно сделать только с небольшими изменениями в включенном текстовом файле благодаря новому "сырые" строковые литералы:

в 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 ld linker script.
  2. преобразуйте файл в последовательность символьных констант, которые могут инициализировать массив. Обратите внимание, что вы не можете просто сделать "" и охватить несколько строк. Вам понадобится символ продолжения строки (\), побег " символы и другие, чтобы сделать эту работу. Проще просто написать небольшую программу для преобразования байтов в последовательность типа '\xFF', '\xAB', ...., '' (или используйте инструмент unix xxd описано другим ответом, если он у вас есть!):

код:

#include <stdio.h>

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

(Не проверял). Тогда сделай:

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

где данные.h генерируется

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

ОК, вдохновленный Daemin это!--5--> сообщение Я проверил следующий простой пример:

а.данные:

"this is test\n file\n"

что может сработать, если вы сделаете что-то вроде:

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

конечно, вам придется быть осторожным с тем, что на самом деле находится в файле, убедившись, что нет двойных кавычек, что все соответствующие символы экранированы и т. д.

поэтому было бы проще, если бы вы просто загрузили текст из файла во время выполнения или вставили текст непосредственно в код.

Если вы все еще хотите текст в другом файле, вы можете иметь его там, но это должно быть представлен там в виде строки. Вы бы использовали код, как указано выше, но без двойных кавычек в нем. Например:

"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) в качестве имени переменной. The char array и zero terminated, а длина данных хранится в $variableName_length

#!/bin/bash

fileSize ()

{

    [ -e "" ]  && {

        set -- `ls -l ""`;

        echo ;

    }

}

echo unsigned char '[] = {'
./xtr -fhex -p 0x -s ', ' < "";
echo '0x00'
echo '};';
echo '';
echo unsigned long int _length = $(fileSize "")';'

ВЫ МОЖЕТЕ ПОЛУЧИТЬ XTR ЗДЕСЬ xtr (символ экстраполятор) является GPLV3


вы можете сделать это с помощью objcopy:

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

Теперь у вас есть объектный файл, который вы можете связать с исполняемым файлом, который содержит символы начала, конца и размера содержимого из myfile.txt.


мне нравится ответить kayahr по. Если вы не хотите касаться входных файлов однако, и если вы используете CMake, вы можете добавить разделитель последовательности символов в файле. Например, следующий код CMake копирует входные файлы и соответствующим образом обертывает их содержимое:

function(make_includable input_file output_file)
    file(READ ${input_file} content)
    set(delim "for_c++_include")
    set(content "R\"${delim}(\n${content})${delim}\"")
    file(WRITE ${output_file} "${content}")
endfunction(make_includable)

# Use like
make_includable(external/shaders/cool.frag generated/cool.frag)

затем включите в c++ вот так:

constexpr char *test =
#include "generated/cool.frag"
;

я переосмыслил xxd в python3, исправляя все раздражения xxd:

  • Const правильность
  • тип данных длины строки: int → size_t
  • null termination (в случае, если вы можете этого захотеть)
  • C строка совместима: падение unsigned в массиве.
  • меньший, читаемый вывод, как вы бы его написали: печатаемый ascii выводится как есть; другие байты закодированы шестнадцатью.

вот скрипт, отфильтрованный сам по себе, так что вы можете видеть, что он делает:

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);
}

даже если это можно сделать во время компиляции (я не думаю, что это вообще возможно), текст, вероятно, будет предварительно обработанным заголовком, а не содержимым файлов дословно. Я ожидаю, что вам придется загружать текст из файла во время выполнения или выполнять неприятное задание cut-n-paste.


в X.ч

"this is a "
"buncha text"

в Main.c

#include <stdio.h>
int main(void)
{
    char *textFileContents =
#include "x.h"
    ;

    printf("%s\n", textFileContents);

    return 0
}

должен выполнить работу.


ответ Hasturkun с использованием опции xxd-i превосходен. Если вы хотите включить процесс преобразования (text -> hex include file) непосредственно в свою сборку hexdump.c tool / library недавно добавила возможность, аналогичную опции xxd-i (она не дает вам полный заголовок - Вам нужно предоставить определение массива символов - но это имеет то преимущество, что вы можете выбрать имя символа array):

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

это лицензия намного более "стандартная", чем xxd, и очень либеральная - пример использования ее для встраивания файла init в программу можно увидеть в CMakeLists.txt и схема.c файлы здесь:

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

есть плюсы и минусы как для включения сгенерированных файлов в исходные деревья, так и для связывания утилит - как справиться с этим будет зависеть от конкретных целей и потребностей вашего проекта. шестнадцатеричного представления.c открывает опцию связывания для этого приложения.


Я думаю, что это невозможно только с компилятором и препроцессором. НКУ позволяет этого:

#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"

Почему бы не связать текст в программу и использовать его в качестве глобальной переменной! вот пример. Я рассматриваю возможность использования этого для включения открытых файлов шейдеров GL в исполняемый файл, так как шейдеры GL должны быть скомпилированы для GPU во время выполнения.


у меня были похожие проблемы, и для небольших файлов вышеупомянутое решение Johannes Schaub работало для меня как шарм.

однако для файлов, которые немного больше, он столкнулся с проблемами с ограничением массива символов компилятора. Поэтому я написал небольшое приложение-кодировщик, которое преобразует содержимое файла в 2D-символьный массив кусков одинакового размера (и, возможно, заполнение нулей). Он производит выходные текстовые файлы с данными 2D-массива, как это:

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

где 4 фактически является переменной MAX_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;
}