Проблема с использованием reinterpret_cast‹› в С++

Я пытаюсь преобразовать поток данных в структуру, поскольку поток данных состоит из сообщений с фиксированной шириной, и каждое сообщение также имеет полные определенные поля с фиксированной шириной. Я планировал создать структуру, а затем использовать reinterpret_cast для приведения указателя на поток данных к структуре для получения полей. Я сделал тестовый код и получил странные результаты. Может ли кто-нибудь объяснить, почему я получаю это или как исправить код. (поток данных будет смешанным двоичным и буквенно-цифровым, но я просто тестирую строки)

#pragma pack(push,1)
struct Header 
{
    char msgType[1];
    char filler[1];
    char third[1];
    char fourth[1];
};
#pragma pack(pop)

int main(void)
{
    cout << sizeof(Header) << endl;

    char* data = "four";
    Header* header = reinterpret_cast<Header*>(data);
    cout << header->msgType << endl;
    cout << header ->filler << endl;
    cout << header->third << endl;
    cout << header->fourth << endl;
    return 0;
}

Результат, который приближается,

4
four
our
ur
r

Я думаю, что четыре, наш и ур печатаются, так как он не может найти нулевой терминатор. Как обойти проблему нулевого терминатора?


person randomThought    schedule 09.12.2009    source источник
comment
Помните, что компилятору разрешено добавлять пробелы между полями структуры. Это означает, что memcpy и fread могут работать не так, как ожидалось. Самая безопасная процедура — скопировать данные в элементы по отдельности. Используя этот метод, вы можете добавить этот дополнительный символ в массивы символов для нулевого терминатора или, что еще лучше, заменить массивы символов на std::string.   -  person Thomas Matthews    schedule 09.12.2009


Ответы (5)


Чтобы иметь возможность печатать массив символов и отличать его от строки с завершающим нулем, вам нужны другие определения operator<<:

template< size_t N >
std::ostream& operator<<( std::ostream& out, char (&array)[N] ) {
     for( size_t i = 0; i != N; ++i ) out << array[i];
     return out;
}
person xtofl    schedule 09.12.2009
comment
Это интересно. Что делать, если некоторые строки заканчиваются нулем, а некоторые нет, поскольку поле длиной 8 может иметь только 1 символ и остальные нули для его заполнения, но могут даже иметь все 8 символов без нулевого завершения. Это напечатает 8 символов в обоих случаях. как я могу напечатать 8, если он заполнен, и меньше 8, если он завершается нулем? - person randomThought; 09.12.2009
comment
Вы можете добавить if (array[i]==0 ) break; в тело цикла. - person xtofl; 09.12.2009
comment
я изменил оператор цикла на for( size_t i = 0; (i != N && array[i] != '\0'); ++i ) out ‹‹ array[i]; и это работает довольно хорошо. Спасибо - person randomThought; 09.12.2009

Вы правы насчет отсутствия нулевого терминатора. Причина, по которой он снова печатает «ur», заключается в том, что вы повторили заголовок-> третий вместо заголовка-> четвертый. Вместо «char[1]» почему бы просто не объявить эти переменные как «char»?

struct Header 
{
    char msgType;
    char filler;
    char third;
    char fourth;
};
person Paul Tomblin    schedule 09.12.2009
comment
У меня есть около 20 различных сообщений, которые имеют 300 или более полей вместе, поэтому я использую скрипт для создания структур для меня, и скрипт помещает туда 1, так как есть некоторые поля, которые должны быть шириной всего 2 символа и так далее. - person randomThought; 09.12.2009

Проблема не в reinterpret_cast (хотя использовать его — очень плохая идея), а в типах вещей в структуре. Они должны быть типа «char», а не типа «char[1]».

person Community    schedule 09.12.2009
comment
чар работает. Что делать, если я хочу, чтобы первое поле было длиной 2 символа, а остальные только 1. что мне тогда там использовать? - person randomThought; 09.12.2009
comment
Тогда вы набиты, если вы все еще хотите использовать reinterpret_cast для строки, которая не содержит нулевых терминаторов. Если он может содержать нулевые терминаторы, сделайте первый char[3] и строку fo\0ur или что-то подобное. - person ; 09.12.2009
comment
есть предложения по лучшей альтернативе reinterpret_cast? - person randomThought; 09.12.2009
comment
Трудно сказать, не зная больше о вашей проблеме. Но предоставление вашим структурам конструктора, который явно анализирует входную строку, было бы началом. - person ; 09.12.2009

По моему опыту, использование #pragma pack вызывает головную боль — отчасти из-за того, что компилятор неправильно выталкивает, но также из-за того, что разработчики забывают вставить один заголовок. Одна такая ошибка, и структуры в конечном итоге определяются по-разному в зависимости от того, какие заголовки order включаются в единицу компиляции. Это кошмар отладки.

По этой причине я стараюсь не делать наложения памяти - вы не можете быть уверены, что ваша структура правильно выровнена с ожидаемыми данными. Вместо этого я создаю структуры (или классы), которые содержат данные из сообщения в «родном» формате C++. Например, вам не нужно определять поле «заполнитель», если оно существует только для целей выравнивания. И, возможно, более разумно, чтобы тип поля был int, чем char[4]. Как можно быстрее переводите поток данных в «родной» тип.

person Dan    schedule 09.12.2009
comment
наполнитель определен в протоколе сообщений, с которым я работаю. - person randomThought; 09.12.2009
comment
Я так понимаю в представлении байтового потока есть байт-заполнитель. Но могу поспорить, что в коде вашего приложения вам никогда не понадобится устанавливать этот байт-заполнитель или проверять его значение. Это просто прокладка. Если бы вы вручную извлекали байты из потока в свои собственные структуры, не было бы необходимости копировать байт-заполнитель, вы могли бы просто пропустить его в потоке. Вот что я пытался сказать. - person Dan; 09.12.2009

Предполагая, что вы хотите продолжать использовать наложенную структуру (что разумно, поскольку она позволяет избежать копирования в коде Алексея), вы можете заменить необработанные массивы символов оболочкой, подобной следующей:

template <int N> struct FixedStr {
    char v[N];
};

template <int N>
std::ostream& operator<<( std::ostream& out, FixedStr const &str) {
    char const *nul = (char const *)memchr(str.v, 0, N);
    int n = (nul == NULL) ? N : nul-str.v;
    out.write(str.v, n);
    return out;
}

Тогда ваши сгенерированные структуры будут выглядеть так:

struct Header 
{
    FixedStr<1> msgType;
    FixedStr<1> filler;
    FixedStr<1> third;
    FixedStr<40> forty;
};

и ваш существующий код должен работать нормально.

NB. вы можете добавить методы к FixedStr, если хотите (например, std::string FixedStr::toString()), просто не добавляйте виртуальные методы или наследование, и они будут нормально накладываться.

person Useless    schedule 09.12.2009
comment
Это может сработать, но мне понадобятся структуры с элементами разного размера в диапазоне от 1 до 40, поэтому шаблон там не будет работать. - person randomThought; 09.12.2009
comment
Вы имеете в виду FixedStr‹1› на FixedStr‹40›? Вы просто перемещаете ширину поля из квадратных скобок в своем коде в угловые скобки в моем. - person Useless; 09.12.2009
comment
... также добавлена ​​поддержка полей с нулевым заполнением. - person Useless; 09.12.2009