Не удается правильно memcpy массив символов для структуры

Итак, у меня есть конструкция, называемая пакетом

struct Packet {
    unsigned int packet_type;
    wchar_t packet_length[128];
    wchar_t file_name[256];
    wchar_t template_name[256];
    wchar_t file_name_list[1024];
    wchar_t file_data[1024];

    void serialize(char * dat) {
        memcpy(dat, this, sizeof(Packet));
    }

    void deserialize(const char * dat) {
        memcpy(this, dat, sizeof(Packet));
    }
};

Я пытаюсь десирализировать эти данные

{byte[2692]}
[0]    0       unsigned int packet_type; (4 bytes)
[1]    0
[2]    0
[3]    0
[4]    50 '2'  wchar_t packet_length[128]; (128 bytes)
[3]    0
[5]    54 '6'
[3]    0
[6]    57 '9'
[3]    0
[7]    50 '2'
[8]    0
[...]  0
[132]  112 'p'  wchar_t file_name[256]; (256 bytes)
[133]  0
[134]  104 'h'
[...]  0

Но memcpy в deserialze не дает мне имя_файла, но дает длину пакета. Что случилось с этим? Спасибо!

РЕДАКТИРОВАТЬ: Теперь мне ясно, что wchar_t занимает больше места, чем я когда-то думал; однако мне говорят вообще не использовать memcpy?

Я написал этот метод десериализации, и он правильно захватывает данные. Будет ли это по-прежнему вызывать утечку безопасности?

void deserialize(const char * dat) {
        memcpy(&(packet_type), dat, 4);
        memcpy(&(packet_length[0]), dat + 4, 128);
        memcpy(&(file_name[0]), dat + 132, 256);
        memcpy(&(template_name[0]), dat + 388, 256);
        memcpy(&(file_name_list[0]), dat + 644, 1024);
        memcpy(&(file_data[0]), dat + 1668, 1024);
    }

person jacobsgriffith    schedule 23.04.2014    source источник
comment
Дважды проверьте размер wchar_t в вашей целевой системе. Бьюсь об заклад, это не 1 байт, поэтому ваши ожидания по размеру неверны. wchar_t file_name[256] должно быть не менее 512 байт, а не 256. Дайте тогда последний раздел вашего вопроса, я уверен, вы смотрите на неправильное смещение в массиве.   -  person Captain Obvlious    schedule 23.04.2014
comment
Я бы предположил, что это в значительной степени неопределенное поведение. Даже если это не так, вы также смешиваете char с wchar_t. Поскольку похоже, что размер wchar_t в вашей системе составляет два байта, а не четыре, я предполагаю, что это выполняется на компьютере с Windows. Ознакомьтесь с wmemcpy и wmemcpy_s.   -  person jliv902    schedule 23.04.2014
comment
Пожалуйста, не используйте магические числа для размеров шрифта (например, 4 для размера беззнакового целого числа). Безопаснее всего использовать sizeof( varname ). Это учитывает не только размер типа varname, но и обрабатывает экземпляры изменяемого типа varname.   -  person Rob K    schedule 23.04.2014


Ответы (2)


Макет вашего массива char предполагает, что размер wchar_t составляет два байта; это не так - вот пример системы, где размер wchar_t равен 4, поэтому размер Packet равен 10756, а не 2692 байтам: (ссылка на демо).

Вот почему ваш трюк memcpy из редактирования представляет собой проблему: он предполагает, что расположение данных в массиве char[] соответствует расположению массивов wchar_t[], с которым оно может совпадать или не совпадать. Если вы знаете, что ваш массив данных содержит двухсимвольные элементы, хранящиеся в формате с прямым порядком байтов (сначала LSB), вы можете написать свою собственную функцию, которая преобразует данные из источника в место назначения и вызывать ее для частей ваших сериализованных данных, например это:

void bytes_to_wchar(wchar_t *dest, const unsigned char* src, size_t length) {
    for (size_t i = 0 ; i != lengt ; i++) {
        dest[i] = src[2*i] | (src[2*i+1] << 8);
    }
}

Теперь вы можете использовать эту функцию для копирования данных в массивы wchar_t независимо от размера wchar_t в целевой системе или порядка байтов целевой системы:

void deserialize(const char * dat) {
    bytes_to_wchar(packet_type,       dat + 0,      4);
    bytes_to_wchar(packet_length[0],  dat + 4,    128);
    bytes_to_wchar(file_name[0],      dat + 132,  256);
    bytes_to_wchar(template_name[0],  dat + 388,  256);
    bytes_to_wchar(file_name_list[0], dat + 644,  1024);
    bytes_to_wchar(file_data[0],      dat + 1668, 1024);
}

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

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

person Sergey Kalinichenko    schedule 23.04.2014

Пожалуйста, не используйте этот метод для сериализации структур. Это совершенно не портативно.

Более того, компилятор также может дополнять, выравнивать или переупорядочивать элементы в зависимости от целевой архитектуры, порядка следования байтов, оптимизации и множества других вещей.

Гораздо более элегантным способом было бы использовать boost ::Сериализация, обеспечивающая переносимость низкоуровневых деталей.

Если, с другой стороны, вы просто хотите проверить свои структуры, вам пригодится отладчик...

person Roberto Reale    schedule 23.04.2014
comment
Это правильный ответ. Это именно тот метод, который вызывает дыры в безопасности и трудно обнаруживаемые ошибки. - person Peter T.B. Brett; 23.04.2014