Я должен признать, что я не эксперт по GDK, но я использовал C в течение многих лет, и я использую C++ в течение многих лет, и кстати. Я программировал с помощью gtkmm (привязка C++ для GTK+) несколько лет назад. Итак, я чувствую, что могу разобраться в том, что может показаться запутанным для ОП.
C струны
В прошлом к обработке строк применялись различные подходы.
Например. в PASCAL строка всегда представляла собой массив из 256 байтов. Первый байт был зарезервирован для длины строки, соответственно. количество других байтов, содержащих символы. Это безопасное решение, но оно имеет недостатки: даже самая короткая строка всегда занимает 256 байт. Хуже того, строка длиной более 255 символов невозможна. В этом случае возможны обходные пути для создания списка строк, но это на самом деле раздражает.
В C это было решено по-другому. Строки могут иметь произвольную длину (при условии, что они помещаются в память компьютера). Сама длина не сохраняется. Вместо этого конец строки отмечается специальным символом '\0'
— байтом со значением 0 — который зарезервирован исключительно для этой цели. Недостаток: длина строки должна храниться отдельно или определяться путем подсчета символов до первого вхождения '\0'
. Стандартная библиотека C предоставляет для этого готовую функцию: strlen() . Это позволяет обрабатывать строки по адресу их первого символа. Следовательно, строка C обрабатывается char*
(или const char*
, если строка C не может быть изменена).
Библиотека C предоставляет некоторые дополнительные функции для поддержки работы со строками C, например, strcpy(). strcpy()
копирует последовательные байты из указателя источника (2-й аргумент) в указатель назначения (1-й аргумент), пока не встретится байт '\0'
. (Он также копируется, но после этого функция завершается.)
Двоичные данные
Двоичные данные (состоящие из байтов с произвольными значениями) можно обрабатывать так же, как строки C. char
— целочисленный тип размером 1 байт. Следовательно, это также подходящий кандидат. Однако двоичные данные могут содержать любое из возможных значений 0 … 255 в любом месте. Таким образом, принцип отмечать конец с помощью '\0'
не работает. Вместо этого длина всегда должна храниться отдельно.
Для обработки двоичных данных часто предпочтительнее использовать unsigned char
. ИМХО, на это есть две существенные причины:
- Это помогает программисту отличать строки C от указателей на произвольные двоичные данные.
char
может быть (по стандарту C, а также по стандарту C++) подписанным или неподписанным (в зависимости от решения поставщика компилятора). Если проблема заключается в подписании значения char
, вместо него следует использовать signed char
или unsigned char
. Для обработки байтов двоичных данных часто удобнее обрабатывать их явно unsigned
.
Стандартная библиотека C предоставляет соотв. функции для обработки двоичных данных, например, например. memcpy(). Обратите внимание, что memcpy()
предоставляет третий параметр для определения размера байтов для копирования из исходного указателя в целевой.
Хранилище
Помимо преимуществ строк C, они сопряжены с определенным бременем: программист несет ответственность за обеспечение всегда достаточного объема памяти. В C есть несколько возможностей:
- используя глобальную переменную с массивом
char
, например. static char data[1024];
- используя локальную переменную с массивом
char
(в функции), например. char data[1024];
- выделение памяти в куче, например.
char *data = malloc(1024);
.
Размер символьных массивов должен быть определен в программе (во время компиляции). Невозможно изменить это во время выполнения программы. (Исключением являются массивы переменной длины. Согласно стандарту C99, они являются необязательными. функция, но такой вещи нет даже в последних стандартах C++, хотя некоторые компиляторы C++ предоставляют их как проприетарное расширение.) Если размер хранилища неизвестен до выполнения, единственным решением является выделение динамической памяти (т.е. size_t n = somehowDetermined(); char *data = malloc(n);
).
Управление достаточным объемом памяти на самом деле звучит не так уж и сложно, но правильная и всегда правильная организация этого показала одну из основных проблем в программах на C и C++ на протяжении многих лет. (C++ унаследовал эту проблему от C. Были добавлены операторы new
и delete
, чтобы обеспечить безопасное размещение памяти в куче, но на самом деле это мало помогло.) годы.
станд::строка
В C++ строка может храниться как std::string
. Это значительно упрощает жизнь со струнами. Например. в то время как строка C должна сравниваться с strcmp()
или чем-то подобным, C++ std::string
предоставляет перегруженный operator==()
, который позволяет интуитивно понятный код, например, например. std::string text = input(); if (text == "exit") exit();
. Еще одним важным преимуществом std::string
является управление внутренней памятью. Строки могут быть добавлены к строкам, вставлены в строки и т. д., и std::string
сам позаботится о правильном распределении внутренней памяти.
Кроме того, std::string
сохраняет размер своего содержимого внутри. (Вероятно, авторы сочли целесообразным потратить дополнительные байты на другой интеграл, чтобы не нужно было считать количество символов при любом извлечении длины строки.) Это делает std::string
подходящим контейнером и для двоичных данных.
Для совместимости с C API std::string
предоставляет лазейку std::string: :c_str(). Он предоставляет необработанное содержимое std::string
в виде строки C. Он допускает, что возвращаемая строка C имеет '\0'
байт после последнего символа, т. е. std::string::c_str()[std::string::size()]
должно возвращать '\0'
. Существует также функция std::string::data(). для доступа к необработанным данным файла std::string
. До C++11 только std::string::c_str()
должен был предоставлять завершающий 0, но не std::string::data()
. С C++11 это было изменено. Теперь не должно быть никакой разницы в возвращаемых значениях std::string::data()
и std::string::c_str()
— обе функции просто возвращают указатель на внутренне сохраненные необработанные данные. Следовательно, std::string
фактически должен помещать символ '\0'
в конце всегда, независимо от содержимого. Это может показаться лишним, но на самом деле речь идет об одном дополнительном байте, и это небольшая цена за большое преимущество в надежности кода.
Код ОП
Учитывая, что OP хочет загружать файлы изображений (которые обычно состоят из произвольных байтов) из памяти, следующий код неверен:
std::string data;
// image file somehow read in
guchar* pixdata = new guchar[data.size()+1];// creating a guchar* with space for image data
strcpy((char*)pixdata,data.c_str());// copying data from string to the guchar*
Для произвольных двоичных данных strcpy()
неверно. Он копируется до тех пор, пока не будет найден первый 0 байт. В данных изображения может быть 0 байтов в любом месте (например, если оно содержит черные пиксели). Таким образом, велика вероятность, что strcpy()
копирует слишком мало байтов. В этом случае memcpy()
будет лучшим выбором.
На самом деле ни то, ни другое не нужно.
std::string data;
уже содержит все, что нужно передать в gdk_pixbuf_loader_write()
, указатель на необработанные данные и размер. Поэтому я предложил полностью отказаться от new[]
и delete
и заменить их на следующие:
std::string data;
// image file somehow read in
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, (const guchar*)data.data(), data.size(), nullptr);
Для последней строки я мог бы также использовать:
gdk_pixbuf_loader_write(loader, (const guchar*)data.c_str(), data.size(), nullptr);
Как я уже объяснил, это не имеет значения, начиная с С++ 11. Я использовал data.data()
, так как он выглядел лучше (учитывая тот факт, что содержимое std::string
представляет собой скорее двоичные данные, чем C-строку).
Примечание о актерском составе const guchar*
:
std::string
хранит внутри динамически выделяемый массив char
. Следовательно, std::string::data()
возвращает const char*
(или char*
). Для gdk_pixbuf_loader_write() требуется const guchar*
в качестве второго аргумента.
guchar — это просто
typedef unsigned char guchar;
Следовательно, const char*
преобразуется в const unsigned char*
. Преобразования типов указателей должны выполняться с осторожностью. (Как правило, это последнее средство для чего-то, что может быть сломано по замыслу, и несет в себе опасность Undefined Behavior — бич каждого программиста на C и C++.) В этом случае преобразование безопасно и законно по стандарту C++. Я нашел еще один ответ, который подробно объясняет это: SO: Могу ли я превратить неподписанный char в char и наоборот?.
ОП пытается исправить код
После того, как я провел несколько подсказок, ОП предложил следующее исправление:
string data = getFileInMem("0.0.0.0:8000/test.txt");
guchar* pixdata = (const guchar*)data.data();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader,pixdata,sizeof(pixdata),nullptr);
К сожалению, это решение содержит новую ошибку: sizeof(pixdata)
. В то время как data.size()
возвращает размер длины строки в data
, оператор sizeof(pixdata)
здесь неправильный выбор.
sizeof — это оператор, и он всегда разрешается во время компиляции — возвращает размер типа на его правой стороне. Его можно вызвать с типом или выражением:
std::cout << sizeof (char) << std::endl;
char c;
std::cout << sizeof c << std::endl;
выведет:
1
1
Таким образом, выражение даже не должно иметь действительное хранилище во время выполнения, поскольку sizeof
всегда разрешается во время компиляции и на основе типа результирующего выражения:
char *c = nullptr;
std::cout << sizeof *c << std::endl;
выведет:
1
Это может показаться удивительным, поскольку *c
выглядит как доступ к содержимому нулевого указателя (что в общем случае является неопределённым поведением). В данном случае это фактически не так. Поскольку оператор sizeof
оценивает тип во время компиляции, сгенерированный код просто содержит результат этой оценки. Таким образом, во время выполнения не происходит *c
, и в коде не появляется Undefined Behavior.
Однако sizeof pixdata
возвращает не размер data
, а только размер указателя guchar*
. Вероятно, это 4, если OP скомпилирован на 32-битной платформе, и 8 для 64-битной платформы, но это всегда одно и то же значение для определенной платформы.
Итак, чтобы исправить это, это должно быть:
string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, pixdata, data.size(), nullptr);
or
string data = getFileInMem("0.0.0.0:8000/test.txt");
const guchar* pixdata = (const guchar*)data.data();
gsize pixdatasize = (gsize)data.size();
GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
gdk_pixbuf_loader_write(loader, pixdata, pixdatasize, nullptr);
Это стало длинным ответом. Это может служить иллюстрацией того, что даже некоторые строки кода на C++ требуют больших базовых знаний для их правильного написания. Следовательно, вполне логично, что программистам начального уровня часто советуют получить хорошую книгу по C++. Я бы не стал настаивать на том, что невозможно выучить C++ другим способом. Тем не менее, хорошая книга по С++, ИМХО, заслуживает внимания. В C++ много подводных камней, большинство из них унаследовано от C, а некоторые специально введены в самом C++.
person
Scheff's Cat
schedule
29.04.2021
strcpy()
- очень плохой выбор для копирования двоичных данных (для изображения). Это для копирования строк с нулевым завершением. В любом месте двоичного изображения могут быть нули. Вместо этого вы должны использоватьmemcpy()
. Вы даже можете передать необработанный указатель данных вstd::string
с помощьюstd::string::c_str()
илиstd::string::data()
. (Конечно, вы должны преобразовать в(const guchar*)
.) Если вы сделаете это, вы должны позаботиться о том, чтобы GTK+ не захотел взять на себя ответственность за данные (но тогдаnew[]
, вероятно, также будет неправильным), и вы должны обеспечить правильное срок службы для вашегоstd::string
. - person Scheff's Cat   schedule 27.04.2021std::ios::binary
в потоке. Вы находитесь в Linux, где это не имеет никакого эффекта, но в Windows отсутствие двоичного флага приводит к внутренней обработке окончаний строк в потоке, что неверно для двоичных данных (где 13 и 10 - это просто двоичные значения без шансов чтобы узнать, кодируют ли они окончания строк или что-то еще). - person Scheff's Cat   schedule 27.04.2021delete[]
что вы создали с помощьюnew[]
, иdelete
что вы создали с помощьюnew
. Сочетаниеguchar* pixdata = new guchar[data.size()+1];
сdelete pixdata;
— это неопределенное поведение. (Кстати, увидев это, мои опасения по поводу владения и срока службы устарели.) Вам действительно следует выбросить своиnew
иdelete
и просто вызватьgdk_pixbuf_loader_write(loader, (const guchar*)data.data(), data.size(), nullptr);
(при условии, чтоdata
имеет типstd::string
). ... И не мешало бы проверить возвращаемое значениеgdk_pixbuf_loader_write()
. - person Scheff's Cat   schedule 27.04.2021const guchar*
сstd::string.data()
вместоstd::string.c_str()
устранило проблему! новый и удаленный были удалены, но я все еще буду помнить ваш совет о различиях междуnew
;new[]
;delete
иdelete[]
тоже буду реализовыватьstd::ios::binary
. Я хотел бы отметить решение, если вы хотите написать решение, я отмечу его как решение, однако, если вы не хотите, будет ли нормально, если я напишу решение и отмечу его? - person Ican'tThinkOfAName   schedule 27.04.2021