«#Include» текстовый файл в программе на C как char []

130

Есть ли способ включить весь текстовый файл в виде строки в программу 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, но он уродлив и ограничен только одним именем переменной, вы можете подсказать мне другой способ сделать это?

Брайан Томпсетт - 莱恩
источник
Посмотрите здесь, чтобы прочитать файл в char []. /programming/410943/reading-a-text-file-into-an-array-in-c Вот несколько советов по использованию макросов препроцессора C. http://gcc.gnu.org/onlinedocs/cpp/Macros.html
Дэниел А. Уайт,
3
почему ты хочешь сделать это? Почему бы не прочитать файл во время выполнения? (Ответ: возможно, потому, что трудно узнать, где находится файл во время выполнения, или, может быть, потому, что должен быть только один файл для установки.)
Джонатан Леффлер,
1
или, возможно, текстовый файл доступен только во время компиляции, например, исходный код.
TMS
2
Иногда вы хотите получить доступ к данным как к отдельным файлам во время разработки, но при этом содержимое будет скомпилировано в ваш двоичный файл. Пример - запуск веб-сервера на Arduino, у которого нет доступа к локальному хранилищу. Вы хотите хранить свои html-файлы отдельно для их редактирования, но во время компиляции они должны существовать в виде строк в вашем источнике.
Geordie

Ответы:

135

Я бы предложил использовать для этого (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;
Hasturkun
источник
18
Просто примечание: char [], созданный xxd, не завершается NULL! поэтому я делаю $ xxd -i <file.txt> file.xxd $ echo ', 0' >> file.xxd и в main.c char file_content [] = {#include "file.xxd"};
3
Я никогда не знал про xxd. Это круто!
1
@eSKay: это происходит непосредственно из вывода xxd, как говорится в ответе. имя массива - это имя входного файла. если вы передаете данные по конвейеру вместо использования входного файла, вместо этого вы получите список шестнадцатеричных значений (без объявления массива или переменной len).
Hasturkun
4
Это чрезвычайно полезно при встраивании шейдеров GLSL.
linello
5
Другой способ добавить завершение 0x00 в код C, созданный xxd:xxd -i file.txt | sed 's/\([0-9a-f]\)$/\0, 0x00/' > file.h
vleo
106

Вопрос касался 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)====="
kayahr
источник
5
Спасибо, я выбрал предложенный здесь метод для встраивания длинных фрагментов sql в мой код C ++ 11. Это позволяет мне аккуратно разделять SQL на отдельные файлы и редактировать их с помощью соответствующей проверки синтаксиса, выделения и т. Д.
YitzikC
1
Это действительно близко к тому, что я хочу. Особенно определяемый пользователем разделитель. Очень полезно. Я хочу пойти еще дальше: есть ли способ полностью удалить префикс R "(и суффикс)" из файла, который вы хотите включить? Я попытался определить два файла с именами bra.in и ket.in с префиксом и суффиксом в них, включая bra.in, file.txt и ket.in один за другим. Но компилятор оценивает содержимое bra.in (это просто R "() перед включением следующего файла. Так что он будет жаловаться. Пожалуйста, дайте мне знать, если кто-нибудь знает, как получить префикс и суффикс из file.txt. Спасибо.
TMS
Я предполагаю, что C ++ не позволит R "(<newline> #include ...)"? Было бы неплохо, если бы файл был загружен во время компиляции, чтобы не требовалось никакого кодирования ... то есть прямо json, xml или csv или что-то еще ...
Брайан Крисман
Вы можете сделать текст необработанного литерала немного более читабельным, если вы используете его 1+R"...в качестве начального разделителя вместо R"..., а затем добавляете перед ним новую строку Line 1. Это преобразует выражение из массива в указатель, но это не проблема, поскольку вы инициализируете указатель, а не массив.
Руслан
14

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

  1. Используйте расширения компилятора / компоновщика для преобразования файла в двоичный файл с правильными символами, указывающими на начало и конец двоичных данных. См. Этот ответ: Включите двоичный файл в сценарий компоновщика GNU ld .
  2. Преобразуйте ваш файл в последовательность символьных констант, которые могут инициализировать массив. Обратите внимание, вы не можете просто сделать "" и охватить несколько строк. Для этого вам понадобится символ продолжения строки ( \), 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
Йоханнес Шауб - litb
источник
1
последняя строка должна, вероятно, читать «cat file.bin | ./bin2c> data.h» или «./bin2c <file.bin> data.h»
Hasturkun
Я использовал codeproject.com/Tips/845393/… для создания шестнадцатеричного файла (в Windows) из двоичного файла, а затем использовал ваше предложение char my_file[] = { #include my_large_file.h };Спасибо!
Someone Somewhere
bin2cэто не то же самое bin2c, что и от debian hxtools, будьте осторожны
ThorSummoner 05
или, если это так, призыв теперь намного более странный:bin2c -H myoutput.h myinput1.txt myinputN.txt
ThorSummoner
9

хорошо, вдохновленный постом Дэмина, я протестировал следующий простой пример:

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

Итак, он работает, но требуются данные, заключенные в кавычки.

Илья
источник
Это то, что я имел в виду в последней части своего ответа.
Daemin 04
цитата, или как там это называется, простите за мой английский
Илья
Это требует, чтобы данные были экранированы C. Я не думаю, что это то, что ищет пост. Если бы у этого был какой-то макрос включения, который C-экранировал содержимое файла, это было бы хорошо.
Брайан Крисман,
8

Мне нравится ответ Каяра. Однако, если вы не хотите касаться входных файлов и если вы используете 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"
;
Мартин Р.
источник
5

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

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

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

Джон Цвинк
источник
1
Вы можете сказать нам, какие будут названия символов?
Mark Ch
@MarkCh: согласно документации, имена символов генерируются из имени входного файла.
John Zwinck 05
Я предполагаю, что это не будет работать на машинах не x86-64, не так ли?
ThorSummoner
2

Вам нужна моя xtrутилита, но вы можете сделать это с помощью bash 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 (character eXTRapolator) является GPLV3


источник
2

Если вы готовы прибегнуть к некоторым грязным уловкам, вы можете проявить творческий подход с необработанными строковыми литералами и #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

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

mattnewport
источник
Я использовал этот подход и для размещения шейдеров OpenGL в текстовых файлах!
yano 05
1

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

  • Постоянная правильность
  • длина строки тип данных: int → size_t
  • Нулевое завершение (на случай, если вы этого захотите)
  • Совместимость со строкой 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);
}
user2394284
источник
1

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

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 ++ в текстовом файле, который вы включаете. Это сделало бы код более аккуратным, потому что в начале файла не так много текста.

Daemin
источник
3
Хорошая идея, но это не сработает, либо у вас есть ошибка, потому что литерал включает новую строку, либо часть #include будет прочитана как строка и не будет выполнена, черт возьми, если вы это сделаете, и проклят, если вы этого не сделаете ... .
Моти
1
@Motti: согласовано - как написано, синтаксически неверно C. Идея интересная - препроцессор C логически является отдельной фазой, но практика такова, что он не запускается, потому что каждая строка во включенном файле будет иметь заканчиваться обратной косой чертой и т. д.
Джонатан Леффлер,
2
Хамм. Мне кажется, вам не нужна обратная косая черта, поскольку большинство компиляторов объединяют смежные строки вместе
EvilTeach
дело в этом ответе ... если бы это было так просто, я не думаю, что OP когда-либо задал бы этот вопрос! -1, потому что наличие этого ответа немного побуждает людей тратить свое время на то, что не работает. Я думаю, мы могли бы убрать отрицательный голос, если бы вы изменили «Что может сработать» на «Для справки, это не работает»
Марк Ч.
@JonathanLeffler После запуска препроцессора он должен быть действительным C или C ++ в зависимости от того, как форматируется file.txt.
Daemin
0

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

Дэниел Полл
источник
0

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

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

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

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

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

starseeker
источник
0

Я думаю, что это невозможно только с компилятором и препроцессором. 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"
не-а-пользователь
источник
Я смотрел, как ты велел мне смотреть. Я не вижу никакой новой информации в вашем ответе (информации, которой нет в других ответах), кроме ссылки на /etc/hostnameспособ встраивания имени машины сборки в строку, которая (даже если бы она сработала) не была бы переносимый, поскольку в Mac OS X нет файла /etc/hostname. Обратите внимание, что при использовании имен макросов, которые начинаются с подчеркивания, за которым следует заглавная буква, используется имя, зарезервированное для реализации, а это A Bad Thing ™.
Джонатан Леффлер
0

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

TechDragon
источник
0

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

Однако для файлов, которые немного больше, возникали проблемы с ограничением массива символов компилятора. Поэтому я написал небольшое приложение-кодировщик, которое преобразует содержимое файла в массив 2D-символов, состоящий из блоков одинакового размера (и, возможно, дополняющих нулей). Он создает выходные текстовые файлы с данными 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», затем можно легко встроить в приложение 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;
}
volzotan
источник
0

Эта проблема меня раздражала, и xxd не работает для моего варианта использования, потому что из-за этого переменная называлась чем-то вроде __home_myname_build_prog_cmakelists_src_autogen, когда я пытался ее скрипт, поэтому я сделал утилиту для решения этой точной проблемы:

https://github.com/Exaeta/brcc

Он генерирует исходный файл и файл заголовка и позволяет вам явно указать имя каждой переменной, чтобы затем вы могли использовать их через std :: begin (имя массива) и std :: end (имя массива).

Я включил его в свой проект cmake так:

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)

С небольшими изменениями, я полагаю, его можно было бы заставить работать и для C.

Exaeta
источник
-1

в xh

"this is a "
"buncha text"

в main.c

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

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

    return 0
}

должен делать свою работу.

EvilTeach
источник
Для нескольких строк вам нужно добавить \ n так: "line 1 \ n" "line 2 \ n"
Superfly Jon
это немного вводит в заблуждение, очевидно, это требует некоторой подготовки текстового файла для добавления кавычек и символов \ n, не работает в общем случае
Марк Ч.