Подтвердить что ты не робот

Можно ли безопасно использовать std::string для двоичных данных в С++ 11?

В Интернете есть несколько сообщений, в которых предлагается использовать std::vector<unsigned char> или что-то подобное для двоичных данных.

Но я предпочел бы вариант std::basic_string для этого, поскольку он предоставляет множество удобных функций манипуляции с строкой. И AFAIK, так как С++ 11, стандарт гарантирует, что все известные реализации С++ 03 уже сделали: std::basic_string хранит его содержимое в памяти.

На первый взгляд тогда std::basic_string<unsigned char> может быть хорошим выбором.

Я не хочу использовать std::basic_string<unsigned char>, потому что почти все функции операционной системы принимают только char*, что делает явным приведение. Кроме того, строковые литералы const char*, поэтому мне потребуется явный приведение к const unsigned char* каждый раз, когда я назначил строковый литерал для моей двоичной строки, чего я также хотел бы избежать. Кроме того, функции для чтения и записи в файлы или сетевые буферы аналогично принимают указатели char* и const char*.

Это оставляет std::string, что в основном является typedef для std::basic_string<char>.

Единственная потенциальная оставшаяся проблема (которую я вижу) с использованием std::string для двоичных данных заключается в том, что std::string использует char (который может быть подписан).

char, signed char и unsigned char - три разных типа, а char может быть либо без знака, либо подписанным.

Итак, когда фактическое значение байта 11111111b возвращается из std::string:operator[] как char, и вы хотите проверить его значение, его значение может быть либо 255 (если char не указано), либо это может быть "что-то отрицательное" (если char подписано, в зависимости от вашего числа).

Аналогично, если вы хотите явно добавить фактическое значение байта 11111111b в std::string, просто добавление (char) (255) может быть определено реализацией (и даже поднять сигнал), если char подписан, а int to char приводит к переполнению.

Итак, есть ли безопасный способ обойти это, что делает std::string двоично-безопасным снова?

В § 3.10/15 говорится:

Если программа пытается получить доступ к сохраненному значению объекта через значение gl другого, чем одно из следующих типов, поведение undefined:

  • [...]
  • тип, который является подписанным или неподписанным типом, соответствующим динамическому типу объекта,
  • [...]
  • a char или неподписанный char тип.

Что, если я правильно понимаю, похоже, позволяет использовать указатель unsigned char* для доступа и управления содержимым std::string и делает это также хорошо определенным. Он просто переинтерпретирует бит-шаблон как unsigned char без каких-либо изменений или потери информации, а именно потому, что для представления значения должны использоваться все биты в char, signed char и unsigned char.

Затем я мог бы использовать эту интерпретацию std::string std::string в качестве средства доступа и изменения значений байтов в диапазоне [0, 255] в четко и переносимом виде независимо от подписанности char.

Это должно решить любые проблемы, связанные с потенциально подписанным char.

Правильны ли мои предположения и выводы?

Кроме того, интерпретация unsigned char* одного и того же шаблона бита (т.е. 11111111b или 10101010b) гарантирована одинаково для всех реализаций? Иначе говоря, стандартная ли гарантия гласит, что "просматривая глаза unsigned char", один и тот же шаблон бит всегда приводит к одному и тому же числовому значению (если число бит в байте одинаковое)?

Можете ли я безопасно (т.е. без каких-либо undefined или определенных реализацией) использовать std::string для хранения и обработки двоичных данных в С++ 11?

4b9b3361

Ответ 1

Преобразование static_cast<char>(uc), где uc имеет тип unsigned char, всегда справедливо: согласно 3.9.1 [basic.fundamental] представление char, signed char и unsigned char идентично с char, идентичным одному из двух других типов:

Объекты, объявленные как символы (char), должны быть достаточно большими, чтобы хранить любой элемент базового набора символов реализаций. Если символ из этого набора сохраняется в символьном объекте, целочисленное значение этого символьного объекта равно значению односимвольной литеральной формы этого символа. Определяется реализация, может ли объект char содержать отрицательные значения. Символы могут быть явно объявлены без знака или подписаны. Обычная char, подписанная char и unsigned char - это три различных типа, которые в совокупности называются узкими типами символов. A char, подписанный char и unsigned char занимают одинаковое количество хранилищ и имеют одинаковые требования к выравниванию (3.11); то есть они имеют одно и то же представление объекта. Для узких типов символов в представлении значения участвуют все биты представления объекта. Для беззнаковых узких типов символов все возможные битовые шаблоны представления значений представляют числа. Эти требования не подходят для других типов. В любой конкретной реализации простой объект char может принимать один и тот же значения в виде подписанного char или unsigned char; какой из них определяется реализацией.

Преобразование значений вне диапазона от unsigned char до char будет, конечно, проблематичным и может вызвать поведение undefined. То есть, пока вы не пытаетесь сохранить смешные значения в std::string, все будет в порядке. Что касается битовых шаблонов, вы можете полагаться на бит n th для перевода в 2 n. Не должно быть проблем с сохранением двоичных данных в std::string при тщательной обработке.

Тем не менее, я не покупаю ваше предположение: обработка двоичных данных в основном требует обработки байтов, которые лучше всего обрабатываются с использованием значений unsigned. В нескольких случаях, когда вам нужно будет конвертировать между char* и unsigned char*, создайте удобные ошибки, если они не будут обработаны явно, а использование char случайно будет отключено! То есть работа с unsigned char предотвратит ошибки. Я также не покупаю в предположении, что вы получаете все эти прекрасные строковые функции: во-первых, вы, как правило, лучше используете алгоритмы, но двоичные данные не являются строковыми данными. Вкратце: рекомендация для std::vector<unsigned char> не просто выходит из воздуха! Преднамеренно избегать строительства трудно найти ловушки в дизайн!

Единственным умеренно обоснованным аргументом в пользу использования char может быть тот, который содержит строковые литералы, но даже не содержит воды с пользовательскими строковыми литералами, введенными в С++ 11:

#include <cstddef>
unsigned char const* operator""_u (char const* s, size_t) 
{
    return reinterpret_cast<unsigned char const*>(s);
}

unsigned char const* hello = "hello"_u;

Ответ 2

Да, ваши предположения верны. Храните двоичные данные в виде последовательности без знака char в std::string.

Ответ 3

У меня возникли проблемы с использованием std::string для обработки двоичных данных в Microsoft Visual Studio. Я видел, что строки становятся необъяснимо усеченными, поэтому я не сделал бы этого, независимо от того, что говорят документы стандартов.