2008-12-06 3 views
20

Мне любопытно о соглашениях для указателей/массивов типа-punning в C++. Вот пример использования, который у меня есть на данный момент:Мнения о типах каламбуров в C++?

Вычислить простую 32-битную контрольную сумму над двоичным блоком данных, рассматривая его как массив из 32-битных целых чисел (мы знаем, что его общая длина кратна 4), а затем суммируя все значения и игнорируя переполнение.

Я бы ожидать такой функции, чтобы выглядеть следующим образом:

uint32_t compute_checksum(const char *data, size_t size) 
{ 
    const uint32_t *udata = /* ??? */; 
    uint32_t checksum = 0; 
    for (size_t i = 0; i != size/4; ++i) 
     checksum += udata[i]; 
    return udata; 
} 

Теперь вопрос у меня есть, что вы считаете «лучший» способ преобразования data в udata?

C-style cast?

udata = (const uint32_t *)data 

C++ cast, который предполагает, что все указатели являются конвертируемыми?

udata = reinterpret_cast<const uint32_t *>(data) 

C++ отливать, что между произвольными типами указателей с использованием промежуточного void*?

udata = static_cast<const uint32_t *>(static_cast<const void *>(data)) 

Кастинг через союз?

union { 
    const uint32_t *udata; 
    const char *cdata; 
}; 
cdata = data; 
// now use udata 

Я полностью понимаю, что это не будет 100% портативный раствор, но я только ожидая, чтобы использовать его на небольшой набор платформ, где я знаю, что это работает (а именно выровненным доступа к памяти и предположения компилятора по указателю сглаживание). Чтобы вы посоветовали?

ответ

12

Что касается стандарта C++, то ответ litb является полностью правильным и самым портативным. Литье const char *data в const uint3_t *, будь то с помощью литья в стиле C, static_cast, или reinterpret_cast, нарушает строгие правила псевдонимов (см. Understanding Strict Aliasing). Если вы скомпилируете с полной оптимизацией, есть хороший шанс, что код не будет в порядке.

Кастинг через соединение (например, my_reint) является лучшим решением, хотя он технически нарушает правило, которое, если вы пишете в объединение через один член и читаете его через другое, приводит к неопределенному поведению. Однако практически все компиляторы поддерживают это, и это приводит к ожидаемому результату. Если вы абсолютно согласны со стандартными 100%, переходите к методу смещения бит. В противном случае, я бы порекомендовал пойти с кастинг через объединение, что, скорее всего, даст вам лучшую производительность.

+0

Решения litb правильны по стандарту - но, как я уже сказал, я уже рассматриваю конкретные платформы. – Tom 2008-12-07 07:01:01

+0

Я не уверен, почему они downvote это :), но я также не уверен, что мое использование союза - неопределенное поведение. Я знаю, что запись в член и чтение из другого члена - неопределенное поведение. Но в моем case, я указываю на его член, который, как предполагается, имеет действительное значение и читает его тогда. – 2008-12-07 12:44:56

+2

Я не думаю, что этот конкретный пример нарушает строгий псевдоним. char * является особым случаем при строгих правилах псевдонимов - char * никогда не может считаться не псевдонимом указателя на какой-либо другой тип. Но в моем ответе я все еще играю в безопасности: просто не стоит делать char * иначе, чем в других подобных случаях. – 2008-12-07 14:07:43

-4

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

5

Игнорирование эффективность, для простоты кода я хотел бы сделать:

#include <numeric> 
#include <vector> 
#include <cstring> 

uint32_t compute_checksum(const char *data, size_t size) { 
    std::vector<uint32_t> intdata(size/sizeof(uint32_t)); 
    std::memcpy(&intdata[0], data, size); 
    return std::accumulate(intdata.begin(), intdata.end(), 0); 
} 

Я также, как последний ответ LITB в, тот, который сдвигает каждый символ в свою очередь, за исключением того, что, поскольку символ может быть подписан, я думаю, что это нуждается в дополнительной маске:

checksum += ((data[i] && 0xFF) << shift[i % 4]); 

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

Если вы не хотите, чтобы выделить столько дополнительной памяти, то:

uint32_t compute_checksum(const char *data, size_t size) { 
    uint32_t total = 0; 
    for (size_t i = 0; i < size; i += sizeof(uint32_t)) { 
     uint32_t thisone; 
     std::memcpy(&thisone, &data[i], sizeof(uint32_t)); 
     total += thisone; 
    } 
    return total; 
} 

Достаточно оптимизация будет избавиться от тетсру и дополнительной переменной uint32_t полностью на НКУ, а просто прочитать целое значение невыровненное , в том, что самый эффективный способ сделать это на вашей платформе, прямо из исходного массива. Надеюсь, что то же самое относится и к другим «серьезным» компиляторам. Но этот код теперь больше, чем литч, поэтому нет ничего, что можно было бы сказать за него, кроме моего, проще превратить в шаблон функции, который будет работать так же хорошо с uint64_t, а мой работает как родной, а не набирает немного -endian.

Это, конечно, не полностью переносимо. Он предполагает, что представление хранилища символов sizeof (uint32_t) соответствует представлению хранилища uin32_t, как мы хотим. Это подразумевается вопросом, поскольку в нем говорится, что можно «трактовать как» другого. Endian-ness, независимо от того, является ли char 8 битами, и может ли uint32_t использовать все биты в своем представлении хранения, может, очевидно, вторгаться, но вопрос подразумевает, что они не будут.

-3

Я знаю, что этот поток был неактивным на некоторое время, но думал, что я отправлю простую родовую рутину литья для такого рода вещи:

// safely cast between types without breaking strict aliasing rules 
template<typename ReturnType, typename OriginalType> 
ReturnType Cast(OriginalType Variable) 
{ 
    union 
    { 
     OriginalType In; 
     ReturnType  Out; 
    }; 

    In = Variable; 
    return Out; 
} 

// example usage 
int i = 0x3f800000; 
float f = Cast<float>(i); 

Надеется, что это помогает кто-то!

1

Есть мои пятьдесят центов - разные способы сделать это.

#include <iostream> 
#include <string> 
#include <cstring> 

    uint32_t compute_checksum_memcpy(const char *data, size_t size) 
    { 
     uint32_t checksum = 0; 
     for (size_t i = 0; i != size/4; ++i) 
     { 
      // memcpy may be slow, unneeded allocation 
      uint32_t dest; 
      memcpy(&dest,data+i,4); 
      checksum += dest; 
     } 
     return checksum; 
    } 

    uint32_t compute_checksum_address_recast(const char *data, size_t size) 
    { 
     uint32_t checksum = 0; 
     for (size_t i = 0; i != size/4; ++i) 
     { 
      //classic old type punning 
      checksum += *(uint32_t*)(data+i); 
     } 
     return checksum; 
    } 

    uint32_t compute_checksum_union(const char *data, size_t size) 
    { 
     uint32_t checksum = 0; 
     for (size_t i = 0; i != size/4; ++i) 
     { 
      //Syntax hell 
      checksum += *((union{const char* c;uint32_t* i;}){.c=data+i}).i; 
     } 
     return checksum; 
    } 

    // Wrong! 
    uint32_t compute_checksum_deref(const char *data, size_t size) 
    { 
     uint32_t checksum = 0; 
     for (size_t i = 0; i != size/4; ++i) 
     { 
      checksum += *&data[i]; 
     } 
     return checksum; 
    } 

    // Wrong! 
    uint32_t compute_checksum_cast(const char *data, size_t size) 
    { 
     uint32_t checksum = 0; 
     for (size_t i = 0; i != size/4; ++i) 
     { 
      checksum += *(data+i); 
     } 
     return checksum; 
    } 


int main() 
{ 
    const char* data = "ABCDEFGH"; 
    std::cout << compute_checksum_memcpy(data, 8) << " OK\n"; 
    std::cout << compute_checksum_address_recast(data, 8) << " OK\n"; 
    std::cout << compute_checksum_union(data, 8) << " OK\n"; 
    std::cout << compute_checksum_deref(data, 8) << " Fail\n"; 
    std::cout << compute_checksum_cast(data, 8) << " Fail\n"; 
} 
Смежные вопросы