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

Есть ли способ включить весь текстовый файл в виде строки в программе 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);
    }
    

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

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

Ответ 1

Я бы предложил использовать (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;

Ответ 2

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

В С++ выполните следующее:

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)====="

Ответ 3

У вас есть две возможности:

  • Используйте расширения компилятора/компоновщика для преобразования файла в двоичный файл с соответствующими символами, указывающими на начало и конец двоичных данных. См. Этот ответ: Включить двоичный файл с помощью GNU ld linker script.
  • Преобразуйте файл в последовательность символьных констант, которые могут инициализировать массив. Обратите внимание, что вы не можете просто делать "" и охватывать несколько строк. Для этого вам понадобится символ продолжения строки (\), escape " и другие. Легче просто написать небольшую программу для преобразования байтов в последовательность, например '\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
}

(не проверено). Затем выполните:

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

Где data.h генерируется

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

Ответ 4

ok, вдохновленный сообщением Daemin, я протестировал следующий простой пример:

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

Таким образом, он работает, но требует данных, окруженных кавычками.

Ответ 5

Мне нравится ответ на кайар. Если вы не хотите прикасаться к входным файлам, но если вы используете 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)

Затем включите в С++, как это:

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

Ответ 6

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

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

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

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

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

file.txt

"Something evil\n"\
"this way comes!"

main.cpp

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

То есть, в основном, в текстовом файле, который вы включаете, есть строка в стиле C или C++. Это сделает код более аккуратным, потому что в начале файла нет такого большого количества текста.

Ответ 7

Вам нужна моя утилита xtr, но вы можете сделать ее с помощью bash script. Это script я вызываю bin2inc. Первым параметром является имя результирующего char[] variable. Второй параметр - это имя file. Выводится C include file с содержимым содержимого, закодированным (в нижнем регистре hex) в качестве имени переменной. char array zero 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 ЗДЕСЬ xtr (символ eXTRapolator) - это GPLV3

Ответ 8

Вы можете сделать это, используя objcopy:

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

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

Ответ 9

Если вы готовы прибегнуть к некоторым грязным трюкам, вы можете проявить творческий подход с необработанными строковыми литералами и #include для определенных типов файлов.

Например, скажем, я хочу включить некоторые сценарии SQL для SQLite в свой проект и хочу получить подсветку синтаксиса, но не хочу никакой специальной инфраструктуры сборки. У меня может быть этот файл test.sql, который является допустимым SQL для SQLite, где -- начинает комментарий:

--x, R"(--
SELECT * from TestTable
WHERE field = 5
--)"

А потом в моем коде C++ я могу иметь:

int main()
{
    auto x = 0;
    const char* mysql = (
#include "test.sql"
    );

    cout << mysql << endl;
}

Вывод:

--
SELECT * from TestTable
WHERE field = 5
--

Или добавить некоторый код Python из файла test.py, который является допустимым скриптом Python (потому что # запускает комментарий в Python, а pass не используется):

#define pass R"(
pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass )"
pass

А затем в коде C++:

int main()
{
    const char* mypython = (
#include "test.py"
    );

    cout << mypython << endl;
}

Который будет выводить:

pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass

Должна быть возможность воспроизвести похожие трюки для других типов кода, которые вы можете включить в строку. Является ли это хорошей идеей, я не уверен. Это своего рода аккуратный взлом, но, вероятно, не то, что вы хотели бы в реальном производственном коде. Возможно, все будет хорошо для хакерского проекта на выходных.

Ответ 10

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

  • Корректность
  • string length тип данных: int → size_t
  • Нулевое завершение (в случае, если вы этого захотите)
  • C string compatible: Drop 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);
}

Ответ 11

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

Ответ 12

Ответ Hasturkun с использованием опции xxd -i превосходный. Если вы хотите включить процесс преобразования (текст → файл с шестнадцатеричным включением) непосредственно в свою сборку, то инструмент/библиотека hexdump.c недавно добавила возможность, аналогичную опции xxd -i (она не дает вам полный заголовок - вам нужно для обеспечения определения массива char, но это имеет то преимущество, что позволяет выбрать имя массива char):

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

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

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

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

Ответ 13

Я думаю, что это невозможно с компилятором и препроцессором. 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"

Ответ 14

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

Ответ 15

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

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

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 - фактически переменная MAX_CHARS_PER_ARRAY в кодере. Файл с результирующим C-кодом, называемым, например, "main_js_file_data.h", может быть легко интегрирован в приложение С++, например, следующим образом:

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

Ответ 16

в x.h

"this is a "
"buncha text"

в main.c

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

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

    return 0
}

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