2016-12-07 12 views
12

Предположим, у меня есть массив без знака, который представляет собой группу объектов POD (например, прочитанных из сокета или через mmap). Какие типы они представляют и в какой позиции определяются во время выполнения, но мы предполагаем, что каждый из них уже правильно выровнен.Массив байтов в POD

Каков наилучший способ «бросить» эти байты в соответствующий тип POD?

Решение должно либо соответствовать стандарту C++ (скажем,> = C++ 11), либо, по крайней мере, гарантированно работать с g ++> = 4.9, clang ++> = 3.5 и MSVC> = 2015U3. РЕДАКТИРОВАТЬ: В linux, windows, работает под управлением x86/x64 или 32/64-бит.

В идеале я хотел бы сделать что-то вроде этого:

uint8_t buffer[100]; //filled e.g. from network 

switch(buffer[0]) { 
    case 0: process(*reinterpret_cast<Pod1*>(&buffer[4]); break; 
    case 1: process(*reinterpret_cast<Pod2*>(&buffer[8+buffer[1]*4]); break; 
    //... 
} 

или

switch(buffer[0]) { 
    case 0: { 
     auto* ptr = new(&buffer[4]) Pod1; 
     process(*ptr); 
    }break; 
    case 1: { 
     auto* ptr = new(&buffer[8+buffer[1]*4]) Pod2; 
     process(*ptr); 
    }break; 
    //... 
} 

И, кажется, работают, но оба AFAIK неопределенное поведение в C++ 1). И только для полноты: Я в курсе «обычного» решения, чтобы просто скопировать материал в соответствующую локальную переменную:

Pod1 tmp; 
std::copy_n(&buffer[4],sizeof(tmp), reinterpret_cast<uint8_t*>(&tmp));    
process(tmp); 

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


Несколько ума решение, которое я придумал это:

template<class T> 
T* inplace_cast(uint8_t* data) { 
    //checks omitted for brevity 
    T tmp; 
    std::memmove((uint8_t*)&tmp, data, sizeof(tmp)); 
    auto ptr = new(data) T; 
    std::memmove(ptr, (uint8_t*)&tmp, sizeof(tmp)); 
    return ptr; 

} 

г ++ и лязг ++, кажется, чтобы быть в состоянии оптимизировать на эти копии, но я думаю, что это ставит много нагрузки на оптимизатор и может привести к сбою других оптимизаций, не работает с const uint8_t* (хотя я не хочу его изменять), и выглядит просто ужасно (не думайте, что вы получите этот обзор кода).


1) Первый УБ, потому что она нарушает строгие ступенчатости, вторая, вероятно, УБ (discussed here), так как стандарт просто говорит о том, что результирующий объект не инициализирован и имеет неопределенное значение (вместо гарантируя, что базовая память не затронута). Я считаю, что первый эквивалентный c-код хорошо определен, поэтому компиляторы могут разрешить это для совместимости с c-заголовками, но я не уверен в этом.

+1

Вы не ответили на свой вопрос? 'Каков наилучший способ« бросить »эти байты в соответствующий тип POD?» «Я знаю о« обычном »решении просто скопировать материал в соответствующую локальную переменную. – deviantfan

+0

Если реальный вопрос заключается в том, как решить 'это просто раздражает меня, чтобы знать, что у меня есть правильные биты в соответствующем месте в памяти, но я просто не могу их использовать.", тогда, возможно, C++ не подходит для вас, или, по крайней мере, объекты не являются правильная вещь. Если вам нужны биты, зачем вообще использовать структуры/классы для данных? Просто возьмите массив байтов и измените его так, как хотите. – deviantfan

+1

@deviantfan: Это решение не является «отличным», и я также перечислял некоторые объективные причины, почему я не доволен им (надстройка и модификация на месте). Причина, по которой я использую C++, заключается в том, что он позволяет мне (по большей части) использовать мощные абстракции, с одной стороны, но сходить туда, где это необходимо (я занимаюсь программированием микроконтроллеров). Это одна конкретная ситуация - единственная, с которой я столкнулся, где C++ не дает мне достаточного контроля. – MikeMB

ответ

0

Самый правильный способ заключается в создании (временную) переменную нужного класса POD, и использовать memcpy() для копирования данных из буфера в эту переменную:

switch(buffer[0]) { 
    case 0: { 
     Pod1 var; 
     std::memcpy(&var, &buffer[4], sizeof var); 
     process(var); 
     break; 
    } 
    case 1: { 
     Pod2 var; 
     std::memcpy(&var, &buffer[8 + buffer[1] * 4], sizeof var); 
     process(var); 
     break; 
    } 
    //... 
} 

Там основной причиной этого является из-за проблем с выравниванием: данные в буфере могут быть неправильно выровнены для используемого типа POD. Создание копии устраняет эту проблему. Он также позволяет вам использовать переменную, даже если сетевой буфер больше не доступен.

Только если вы абсолютно уверены, что данные правильно выровнены, вы можете использовать первое предоставленное вами решение.

(Если вы читаете данные из сети, вы всегда должны проверить, что данные действительны в первую очередь и что вы не будете читать за пределами своего буфера. Например, с помощью &buffer[8 + buffer[1] * 4] вы должны проверить, что начало этот адрес плюс размер Pod2 не превышает длину буфера. к счастью, вы используете uint8_t, в противном случае вы должны также проверить, что buffer[1] не является отрицательной.)

+0

Я уже сказал, что я принимаю согласованные данные и согласно стандарту, вы не можете использовать первое решение, даже если данные выровнены. Если вы говорите, что конкретный компилятор официально поддерживает это, я бы попросил вас назвать его. И да, я пропустил какие-то проверки ради краткости. Простите за это. – MikeMB

+1

memcpy - правильное решение здесь. См. Https://gist.github.com/socantre/3472964 – user673679

-1

используя объединение позволяет избежать правил антиалиасинга. На самом деле для этого нужны профсоюзы. Таким образом, указывание на тип объединения из типа, являющегося частью объединения, явно разрешено в стандарте C++ (пункт 3.10.10.6). То же самое допустимо в стандарте C (6.5.7).

Следовательно, в зависимости от других свойств соответствующий эквивалент вашего образца может быть следующим.

union to_pod { 
    uint8_t buffer[100]; 
    Pod1 pod1; 
    Pod1 pod2; 
    //... 
}; 

uint8_t buffer[100]; //filled e.g. from network 

switch(buffer[0]) { 
    case 0: process(reinterpret_cast<to_pod*>(buffer + 4)->pod1); break; 
    case 1: process(reinterpret_cast<to_pod*>(buffer + 8 + buffer[1]*4)->pod2); break; 
    //... 
} 
+0

Может быть разрешено использование типов указателей, но чтение из члена объединения, которое ранее не было записано, - это UB в C++. – MikeMB

+0

Где это написано? –

+0

В стандарте C++;). Извините, я сейчас нахожусь на мобильном телефоне, поэтому поиск точного местоположения будет несколько болезненным. Это - кстати - разница между c и C++. Вы также найдете много ответов здесь, на SO, говоря то же самое. – MikeMB