"#Include" um arquivo de texto em um programa C como um caractere []

130

Existe uma maneira de incluir um arquivo de texto inteiro como uma string em um programa C em tempo de compilação?

algo como:

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

obtendo um pequeno programa que imprime no stdout "Este é um pequeno arquivo de texto"

No momento, usei um script python hackish, mas é feio e limitado a apenas um nome de variável. Você pode me dizer outra maneira de fazer isso?

Brian Tompsett - 汤 莱恩
fonte
Dê uma olhada aqui para ler um arquivo em um char []. /programming/410943/reading-a-text-file-into-an-array-in-c Aqui estão algumas dicas para usar as macros do pré-processador C. http://gcc.gnu.org/onlinedocs/cpp/Macros.html
Daniel A. White
3
Por que você quer fazer isso? Por que não ler o arquivo em tempo de execução? (Resposta: talvez porque é difícil saber por onde o arquivo está em tempo de execução, ou talvez porque não deve ser apenas um arquivo para instalar.)
Jonathan Leffler
ou talvez o arquivo de texto esteja disponível apenas no momento da compilação, como código fonte.
TMS
1
Às vezes, você deseja acessar os dados como arquivos separados no momento do desenvolvimento, mas tem o conteúdo compilado no seu binário. O exemplo está executando um servidor Web em um Arduino que não tem acesso ao armazenamento local. Você deseja manter seus arquivos html separados para editá-los, mas no momento da compilação eles precisam existir como cadeias de caracteres em sua fonte.
Geordie

Respostas:

134

Eu sugiro usar (unix util) xxd para isso. você pode usá-lo assim

$ echo hello world > a
$ xxd -i a

saídas:

unsigned char a[] = {
  0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a
};
unsigned int a_len = 12;
Hasturkun
fonte
18
Apenas uma observação: o char [] criado por xxd não é terminado por NULL! então eu faço $ xxd -i <arquivo.txt> arquivo.xxd $ echo ', 0' >> arquivo.xxd e no arquivo main.c char file_content [] = {#include "file.xxd"};
2
Eu nunca soube sobre xxd. É incrivel!
1
@eSKay: isso vem diretamente da saída de xxd, como a resposta diz. o nome da matriz é o nome do arquivo de entrada. se você estiver inserindo dados em vez de usar um arquivo de entrada, obterá uma lista de valores hexadecimais (sem a declaração da matriz ou a variável len).
Hasturkun 21/03/10
4
Isso é extremamente útil ao incorporar shaders GLSL.
linello 18/01
5
Outra maneira de adicionar 0x00 terminação para xxd produzido código C:xxd -i file.txt | sed 's/\([0-9a-f]\)$/\0, 0x00/' > file.h
vleo
104

A pergunta era sobre C, mas, se alguém tentar fazer isso com o C ++ 11, isso poderá ser feito com apenas pequenas alterações no arquivo de texto incluído, graças aos novos literais de cadeia de caracteres brutos :

Em C ++, faça o seguinte:

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

No arquivo de texto, faça o seguinte:

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

Portanto, deve haver apenas um prefixo na parte superior do arquivo e um sufixo no final dele. Entre elas, você pode fazer o que quiser, sem escapes especiais, desde que não precise da sequência de caracteres )". Mas mesmo isso pode funcionar se você especificar seu próprio delimitador personalizado:

R"=====(Line 1
Line 2
Line 3
Now you can use "( and )" in the text file, too.
Line 5
Line 6)====="
kayahr
fonte
5
Obrigado, escolhi o método proposto aqui para incorporar longos fragmentos de sql no meu código C ++ 11. Isso me permite manter o SQL claramente separado em seus próprios arquivos e editá-los com verificação de sintaxe, destaque etc.
YitzikC
1
Isso é realmente perto do que eu quero. Especialmente o delimitador definido pelo usuário. Muito útil. Eu quero ir um passo adiante: existe uma maneira de remover completamente o prefixo R "(e sufixo)" do arquivo que você deseja incluir? Tentei definir dois arquivos chamados bra.in e ket.in com o prefixo e o sufixo, incluindo bra.in, file.txt e ket.in, um por um. Mas o compilador avalia o conteúdo de bra.in (que é apenas R "() antes de incluir o próximo arquivo. Portanto, ele irá reclamar. Informe-me se alguém souber como usar o prefixo e o sufixo do arquivo.txt. Obrigado.
TMS
Eu estou supondo que C ++ não permitiria R "(<newline> #include ...)"? Seria bom ter o arquivo sendo tempo de compilação-ingerido para não exigir qualquer codificação que seja .... ou seja json reta ou XML ou CSV ou o que não ..
Brian Chrisman
Você pode tornar o texto do literal bruto um pouco mais legível se você usar 1+R"...como o delimitador inicial em vez de R"...e depois colocar uma nova linha antes Line 1. Isso transformará a expressão de uma matriz em um ponteiro, mas isso não é realmente um problema aqui, pois você está inicializando um ponteiro, não uma matriz.
Ruslan
14

Você tem duas possibilidades:

  1. Use as extensões do compilador / vinculador para converter um arquivo em um arquivo binário, com os símbolos adequados apontando para o início e o fim dos dados binários. Veja esta resposta: Inclua arquivo binário com o script do vinculador GNU ld .
  2. Converta seu arquivo em uma sequência de constantes de caracteres que podem inicializar uma matriz. Note que você não pode "" simplesmente fazer e abranger várias linhas. Você precisaria de um caractere de continuação de linha ( \), "caracteres de escape e outros para fazer esse trabalho. É mais fácil escrever um pequeno programa para converter os bytes em uma sequência como '\xFF', '\xAB', ...., '\0'(ou usar a ferramenta unix xxddescrita por outra resposta, se você a tiver disponível!):

Código:

#include <stdio.h>

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

(não testado). Então faça:

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

Onde data.h é gerado por

cat file.bin | ./bin2c > data.h
Johannes Schaub - litb
fonte
1
a última linha provavelmente deve ser "cat file.bin | ./bin2c> data.h" ou "./bin2c <file.bin> data.h"
Hasturkun
Usei codeproject.com/Tips/845393/… para criar um arquivo hexadecimal (no Windows) a partir de um binário e, em seguida, usei sua sugestão de char my_file[] = { #include my_large_file.h };Obrigado!
Alguém em algum lugar
bin2cnão é o mesmo bin2c do debian's hxtools, cuidado!
ThorSummoner 5/19/19
ou, se for, a invocação é muito mais estranha agora:bin2c -H myoutput.h myinput1.txt myinputN.txt
ThorSummoner 5/10/19
9

ok, inspirado no post de Daemin, testei o seguinte exemplo simples:

a.data:

"this is test\n file\n"

test.c:

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

gcc -E test.c output:

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

Portanto, está funcionando, mas exige dados entre aspas.

Ilya
fonte
Isso é o que eu estava aludindo na última parte da minha resposta.
Daemin 4/01/09
cotação, ou o que é chamado, o perdão meu Inglês
Ilya
Isso requer que os dados sejam escapados em C. Eu não acho que é isso que o post está procurando. Se isso tivesse algum tipo de macro de inclusão que escapasse em C ao conteúdo do arquivo, tudo bem.
22719 Brian Brianman
8

Eu gosto da resposta do kayahr. No entanto, se você não quiser tocar nos arquivos de entrada e se estiver usando o CMake , poderá adicionar as seqüências de caracteres de delimitação no arquivo. O seguinte código do CMake, por exemplo, copia os arquivos de entrada e agrupa o conteúdo adequadamente:

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)

Em seguida, inclua no c ++ assim:

constexpr char *test =
#include "generated/cool.frag"
;
Martin R.
fonte
5

Você pode fazer isso usando objcopy:

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

Agora você tem um arquivo de objeto que pode ser vinculado ao seu executável, que contém símbolos para o início, fim e tamanho do conteúdo myfile.txt.

John Zwinck
fonte
1
você pode nos dizer quais serão os nomes dos símbolos?
Mark Ch
@ MarkCh: de acordo com os documentos, os nomes dos símbolos são gerados a partir do nome do arquivo de entrada.
John Zwinck
Eu estou supondo que isso não funcionará em máquinas que não sejam x86-64, não é?
ThorSummoner
2

Você precisa do meu xtrutilitário, mas pode fazê-lo com um bash script. Este é um script que eu chamo bin2inc. O primeiro parâmetro é o nome do resultante char[] variable. O segundo parâmetro é o nome do file. A saída é C include filecom o conteúdo do arquivo codificado (em minúsculas hex) como o nome da variável fornecido. O char arrayé zero terminated, e o comprimento dos dados é armazenado em$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")';'

VOCÊ PODE OBTER XTR AQUI xtr (eXTRapolator de caracteres) é GPLV3


fonte
2

Se você estiver disposto a recorrer a alguns truques sujos, poderá ser criativo com literais de string brutos e #includepara certos tipos de arquivos.

Por exemplo, digamos que eu queira incluir alguns scripts SQL para SQLite no meu projeto e que eu queira obter destaque de sintaxe, mas não quero nenhuma infraestrutura de construção especial. Eu posso ter este arquivo test.sqlque é SQL válido para SQLite, onde --inicia um comentário:

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

E então, no meu código C ++, posso ter:

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

    cout << mysql << endl;
}

A saída é:

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

Ou, para incluir algum código Python de um arquivo test.pyque é um script Python válido (porque #inicia um comentário no Python e passnão funciona):

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

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

E então no código C ++:

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

    cout << mypython << endl;
}

Qual saída:

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

myfunc()
#undef pass
#define pass

Deve ser possível executar truques semelhantes para vários outros tipos de código que você pode querer incluir como uma string. Se é uma boa ideia ou não, não tenho certeza. É uma espécie de truque, mas provavelmente não é algo que você deseja em um código de produção real. Pode ser bom para um projeto de hack de fim de semana.

mattnewport
fonte
Eu usei essa abordagem para colocar o OpenGL Shaders em arquivos de texto também!
yano
1

Reimplementei o xxd no python3, corrigindo todos os aborrecimentos do xxd:

  • Const correção
  • tipo de dados comprimento da cadeia: int → size_t
  • Rescisão nula (no caso de você querer isso)
  • Compatível com string C: solte unsignedna matriz.
  • Saída menor e legível, como você teria escrito: Ascii imprimível é produzido como está; outros bytes são codificados em hexadecimal.

Aqui está o script, filtrado por si só, para que você possa ver o que ele faz:

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;

Uso (extrai o script):

#include <stdio.h>

extern const char pyxxd[];
extern const size_t pyxxd_len;

int main()
{
    fwrite(pyxxd, 1, pyxxd_len, stdout);
}
user2394284
fonte
1

O que pode funcionar é se você fizer algo como:

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

É claro que você terá que ter cuidado com o que está realmente no arquivo , certificando-se de que não haja aspas duplas, que todos os caracteres apropriados sejam escapados etc.

Portanto, pode ser mais fácil se você apenas carregar o texto de um arquivo em tempo de execução ou incorporar o texto diretamente no código.

Se você ainda quisesse o texto em outro arquivo, poderia tê-lo lá, mas ele teria que ser representado lá como uma string. Você usaria o código como acima, mas sem aspas duplas. Por exemplo:

file.txt

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

main.cpp

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

Então, basicamente, ter uma string de estilo C ou C ++ em um arquivo de texto que você incluir. Isso tornaria o código mais limpo, porque não há muito texto no início do arquivo.

Daemin
fonte
3
Boa ideia, mas não vai funcionar, ou você tem um erro porque o literal inclui uma nova linha ou a parte #include será lida como uma string e não executada, danado se você o fizer e danado se não o fizer. .
Motti
1
@Motti: concordou - como está escrito, C. sintaticamente inválido. A idéia é interessante - o pré-processador C é logicamente uma fase separada - mas a prática é que ele não decola porque cada linha do arquivo incluído teria para terminar com uma barra invertida, etc.
Jonathan Leffler
2
Humm. Parece-me que você não deve precisar a barra invertida como a maioria dos compiladores irá concatenar strings adjecent juntos
EvilTeach
a coisa com essa resposta é ... se fosse assim tão simples, acho que o OP jamais teria feito a pergunta! -1 porque a presença desta resposta está incentivando levemente as pessoas a perder tempo tentando algo que não funciona. Acho que poderíamos remover o downvote se você mudou "O que pode funcionar" para "Para referência, isto não funciona"
Mark Ch
@JonathanLeffler Após a execução do pré-processador, ele deve ser válido em C ou C ++, dependendo de como o arquivo.txt está formatado.
Daemin 26/07/19
0

Mesmo que isso possa ser feito em tempo de compilação (acho que não em geral), o texto provavelmente será o cabeçalho pré-processado, e não o conteúdo dos arquivos literalmente. Espero que você precise carregar o texto do arquivo em tempo de execução ou fazer um trabalho desagradável de cortar e colar.

Daniel Paull
fonte
0

A resposta de Hasturkun usando a opção xxd -i é excelente. Se você deseja incorporar o processo de conversão (texto -> arquivo de inclusão hexadecimal) diretamente na sua compilação, a ferramenta / biblioteca hexdump.c adicionou recentemente um recurso semelhante à opção -i do xxd (ele não fornece o cabeçalho completo - você precisa para fornecer a definição da matriz de caracteres, mas com a vantagem de permitir que você escolha o nome da matriz de caracteres):

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

Sua licença é muito mais "padrão" que o xxd e é muito liberal - um exemplo de como usá-lo para incorporar um arquivo init em um programa pode ser visto nos arquivos CMakeLists.txt e schema.c aqui:

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

Existem prós e contras na inclusão de arquivos gerados nas árvores de origem e nos utilitários de empacotamento - como lidar com isso dependerá dos objetivos e necessidades específicos do seu projeto. O hexdump.c abre a opção de empacotamento para este aplicativo.

starseeker
fonte
0

Eu acho que não é possível apenas com o compilador e o pré-processador. O gcc permite isso:

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

Mas infelizmente não é isso:

#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)

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

O erro é:

/etc/hostname: In function init_module’:
/etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF"
não usuário
fonte
Eu olhei, como você me oferece. Não vejo nenhuma informação nova em sua resposta (informações que não estão em outras respostas), além de uma referência a /etc/hostnamecomo uma maneira de incorporar o nome da máquina de construção na string, que (mesmo que funcionasse) não seria portátil, já que o Mac OS X não possui um arquivo /etc/hostname. Observe que o uso de nomes de macro que começam com um sublinhado seguido por uma letra maiúscula está usando um nome reservado para a implementação, que é A Bad Thing ™.
precisa
0

Por que não vincular o texto ao programa e usá-lo como uma variável global! Aqui está um exemplo. Estou pensando em usar isso para incluir arquivos de sombreador Open GL em um executável, pois os sombreadores GL precisam ser compilados para a GPU em tempo de execução.

TechDragon
fonte
0

Eu tive problemas semelhantes e, para arquivos pequenos, a solução de Johannes Schaub acima mencionada funcionou como um encanto para mim.

No entanto, para arquivos um pouco maiores, houve problemas com o limite da matriz de caracteres do compilador. Portanto, escrevi um pequeno aplicativo de codificador que converte o conteúdo do arquivo em uma matriz de caracteres 2D de pedaços de tamanho igual (e possivelmente preenchendo zeros). Produz arquivos de texto de saída com dados de matriz 2D como este:

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

onde 4 é realmente uma variável MAX_CHARS_PER_ARRAY no codificador. O arquivo com o código C resultante, chamado, por exemplo "main_js_file_data.h", pode ser facilmente incorporado ao aplicativo C ++, por exemplo:

#include "main_js_file_data.h"

Aqui está o código fonte do codificador:

#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;
}
volzotan
fonte
0

Esse problema estava me irritando e o xxd não funciona no meu caso de uso, porque fez a variável chamada algo como __home_myname_build_prog_cmakelists_src_autogen quando tentei fazer o script, então criei um utilitário para resolver esse problema exato:

https://github.com/Exaeta/brcc

Ele gera um arquivo de origem e de cabeçalho e permite definir explicitamente o nome de cada variável para que você possa usá-los via std :: begin (nome da matriz) e std :: end (nome da matriz).

Eu o incorporei no meu projeto cmake da seguinte forma:

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.hpp ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.cpp
  COMMAND brcc ${CMAKE_CURRENT_BINARY_DIR}/binary_resources RGAME_BINARY_RESOURCES_HH txt_vertex_shader ${CMAKE_CURRENT_BINARY_DIR}/src/vertex_shader1.glsl
  DEPENDS src/vertex_shader1.glsl)

Com pequenos ajustes, acho que também poderia funcionar para C.

Exaeta
fonte
-1

em xh

"this is a "
"buncha text"

em main.c

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

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

    return 0
}

deveria fazer o trabalho.

EvilTeach
fonte
Para várias linhas, você precisa adicionar \ n para: "linha 1 \ n" "linha 2 \ n"
Superfly Jon
é um pouco enganador, obviamente, isso requer alguma preparação do arquivo de texto para adicionar citações e \ n caracteres, não funciona no caso geral
Mark Ch