Использование указателя для преобразования char* в unsigned char*

Я пишу код, который использует функцию fstream read(), и эта функция ожидает char* в качестве буфера. Позже я хочу работать с байтами в этом буфере как беззнаковые символы, поэтому мне либо придется: 1. объявить буфер как char*, а затем выполнить static_casts для каждого элемента позже, 2. объявить буфер как unsigned char*, а затем выполнить reinterpret_cast, когда я передаю его функции чтения, или 3. объявить буфер как char*, а также создать приведенный указатель, который я использую для доступа к буферу как unsigned char.

Вот фрагмент:

char* buf = new char[512];
unsigned char* ubuf = reinterpret_cast<unsigned char*>(buf);

    fstream myfile;

    myfile.open("foo.img");

    myfile.seekg(446);
    myfile.read(buf, 16);
    //myfile.read(reinterpret_cast<char*>(buf), 16);

int bytes_per_sector = ubuf[1] << 8 | ubuf[0];
...

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


person shwoseph    schedule 01.02.2015    source источник
comment
Это один из очень немногих случаев, когда reinterpret_cast действительно безопасен и имеет смысл.   -  person Konrad Rudolph    schedule 01.02.2015
comment
Просто небольшая побочная проблема: в вашем коде кажется, что нет оправдания динамическому выделению буфера, а не автоматическому. И поскольку вы делаете reinterpret_cast, потому что ни один из других не будет работать, почему бы вместо этого не сделать приведение в стиле c? Это в равной степени безопасно (или небезопасно) и может быть достаточно менее громоздким, так что один указатель (unsigned char* или char*, в зависимости от того, сколько вам нужно) не приводит к слишком большому (если вообще есть) дополнительному коду.   -  person Deduplicator    schedule 01.02.2015
comment
@Дедупликатор Тьфу. Пожалуйста, не рекомендуйте использовать приведения в стиле C. Учитывайте те, которые устарели в C++. Это безопасно в этой ситуации, но гораздо проще просто запретить их сразу и избежать путаницы. И reinterpret_cast, будучи более явным, также делает код более читабельным, поскольку он четко сообщает читателю, какое приведение выполняется.   -  person Konrad Rudolph    schedule 01.02.2015
comment
@KonradRudolph: Больше читать не значит больше читать. Хотя в большинстве случаев приведения типов в стиле C в C++ следует избегать, обоснование (которое в большинстве случаев вполне разумно) действительно здесь плохо работает. Кроме того, всегда помните, что приведения в стиле функций — это просто приведения в стиле C, только отличающиеся от других.   -  person Deduplicator    schedule 01.02.2015
comment
@Deduplicator C++ выполняет приведение типов replace C. Использование приведения C не является ни полезным, ни оправданным, поскольку всегда есть более конкретное приведение C++. Нет никаких причин использовать приведение C. Ваш может быть достаточно менее громоздким, не имеет смысла, поскольку 1. приведение C будет просто делать то, что будет делать соответствующее приведение C++, и 2. в этом случае это ничего.   -  person philipxy    schedule 01.02.2015
comment
@philipxy: Если вы считаете, что сгенерированный машинный код такой же, как я обедаю, о том, что приведения в новом стиле громоздки, то почему вы не программируете на ассемблере, где вам гораздо проще избежать этого вид навалом, чем в HLL? Вернемся к уровню исходного кода C++, и все приведения в новом стиле намного громоздче, чем в старом, вне зависимости от того, переименованы ли они в стиль функций или нет.   -  person Deduplicator    schedule 01.02.2015
comment
@Deduplicator Суть, которую подчеркивали и philipxy, и я, заключается в том, что reinterpret_cast является явным и, следовательно, повышает читаемость, даже если не обеспечивает безопасность типов. reinterpret_cast имеет четко определенное значение. Актерский состав в стиле C, напротив, этого не делает. Это может означать что угодно, поэтому использование его в коде скрывает фактическую семантику от читателя. Общепризнано, что это невероятно плохая идея.   -  person Konrad Rudolph    schedule 01.02.2015
comment
@Deduplicator Я предлагаю узнать о приведениях C ++, правиле «как если» и соответствующих видимых пользователю временных и пространственных гарантиях по определению языка для приведения.   -  person philipxy    schedule 01.02.2015
comment
@KonradRudolph: Если вы утверждаете, что всегда, когда есть более подробный вариант, краткий скрывает семантику, мне интересно, как вы что-то делаете. Инженерия — это поиск компромиссов, и независимо от того, используете ли вы C-Style-cast, function-style-cast или new-style-cast для приведения указателя символа одного вида к другому, нет никакой дополнительной ясности в тщательном подробный. Да, я признал, что в большинстве ситуаций использование приведения нового стиля добавляет ясности и безопасности, но вы действительно должны уважать пределы применимости любых общих советов.   -  person Deduplicator    schedule 01.02.2015
comment
@KonradRudolph: я не совсем согласен, хотя только в том случае, когда совершенно ясно, к какому типу относится источник и, следовательно, что делает актерский состав. (Что не означает, что я не согласен с тем, что жесткие и быстрые правила лучше, особенно потому, что их применение может быть автоматизировано, и что у этого правила не так уж мало недостатков.) Кроме того, поскольку function-style- приведения типов имеют идентичную семантику, они должны рассматриваться как подпадающие под одно и то же обвинение, а это не всегда практично.   -  person Deduplicator    schedule 01.02.2015
comment
... Теперь, если бы мне пришлось перепроектировать С++, я бы с удовольствием понизил класс c-style-cast до неявного, а функциональный стиль до неявного+любой ctor+оператор-преобразования (жаль, что implicit_cast нет в C+ +14), а остальные 4 вида (статические, константные, динамические, реинтерпретируемые) сделать короткими и лаконичными.   -  person Deduplicator    schedule 01.02.2015


Ответы (1)


В этом случае reinterpret_cast подходит по двум причинам:

  1. (Подписанные) типы char и unsigned char должны иметь одинаковое "представление и выравнивание". Это означает, что не будет разницы в данных (они будут точными по битам) или в том, как долго интерпретируется буфер.

  2. Функции чтения файлов обычно используют char* как общий тип доступа к данным. Они не могут использовать void*, потому что тип void имеет неопределенную длину и представление. char, Однако делает. Таким образом, они могут использовать его для чтения/записи серии байтов.

На самом деле файловые функции часто предназначены для переинтерпретации данных как/из чего-то другого. Это позволяет вам иметь такую ​​​​структуру, как

typedef struct structSomeStruct
{
    char name[8]; // a basic, fixed length, string
    unsigned long i; // a 32 bit value
    float x, y, z, w;
} SomeStruct;

Or

class SomeStruct
{
    public:
        char name[8];
        unsigned long i;
        float x, y, z, w;

        SomeStruct()
        {
            // ...
        }
};

И сохраните его в файл, используя что-то вроде:

SomeStruct st;

// populate the st data structure

// file.write(char* start_of_data, size_t number_of_bytes);
file.write(reinterpret_cast<char*>(&st), sizeof(SomeStruct));

И читать аналогично.

person Wolfgang Skyler    schedule 01.02.2015