Вопрос

Мне интересно узнать об условных обозначениях для указателей / массивов типов в 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;
 }

Теперь у меня вопрос: что вы считаете " best " способ конвертировать data в udata?

В стиле C?

udata = (const uint32_t *)data

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

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% переносимое решение, но я ожидаю использовать его только на небольшом наборе платформ, где, как я знаю, оно работает (а именно, не выровненный доступ к памяти и допущения компилятора при наложении указателей). Что бы вы порекомендовали?

Это было полезно?

Решение

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

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

Другие советы

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

#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, который сдвигает каждый символ по очереди, за исключением того, что, поскольку char может быть подписан, я думаю, что ему нужна дополнительная маска:

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

Когда потенциальная проблема связана с типом punning, я предпочитаю не вводить pun, а не пытаться делать это безопасно. Если вы не создаете никаких псевдонимов-указателей различных типов, то вам не нужно беспокоиться о том, что компилятор может сделать с псевдонимами, равно как и программист по обслуживанию, который просматривает ваши множественные 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;
}

Достаточная оптимизация полностью избавит от memcpy и дополнительной переменной uint32_t в gcc и просто прочитает целочисленное значение без выравнивания любым наиболее эффективным способом на вашей платформе прямо из исходного массива. Я надеюсь, что то же самое верно и для других & Quot; серьезных & Quot; компиляторы. Но этот код теперь больше, чем у litb, поэтому сказать о нем немногое, кроме моего, легче превратить в шаблон функции, который будет работать так же хорошо с uint64_t, а мой работает как нативная последовательность, а не выбирает немного -endian.

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

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

#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";
}

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

// 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 );

Надеюсь, это кому-нибудь поможет!

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top