Opinie na temat typowania w C++?

Jestem ciekaw konwencji typowania wskaźników/tablic w C++. Oto przypadek użycia, który mam w tej chwili:

Oblicz prostą 32-bitową sumę kontrolną nad binarną plamą danych, traktując ją jako tablicę 32-bitowych liczb całkowitych (wiemy, że jej całkowita długość jest wielokrotnością 4), a następnie zsumuj wszystkie wartości i Ignoruj przepełnienie.

Spodziewałbym się, że taka funkcja będzie wyglądała tak:

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

Teraz mam pytanie, co uważasz za "najlepszy" sposób na konwersję data na udata?

C-style Obsada?

udata = (const uint32_t *)data

C++ cast, który zakłada, że wszystkie wskaźniki są wymienne?

udata = reinterpret_cast<const uint32_t *>(data)

C++ rzuca to pomiędzy dowolnymi typami wskaźników używając pośredniego void*?

udata = static_cast<const uint32_t *>(static_cast<const void *>(data))
/ Align = "left" /
union {
    const uint32_t *udata;
    const char *cdata;
};
cdata = data;
// now use udata

W pełni zdaję sobie sprawę, że nie będzie to w 100% przenośne rozwiązanie, ale spodziewam się go używać tylko na małym zestawie platform, na których Wiem, że działa (mianowicie niepodpisany dostęp do pamięci i założenia kompilatora na pointerze aliasing). Co byś polecił?

Author: Tom, 2008-12-06

5 answers

Jeśli chodzi o standard C++, ODPOWIEDŹ litb jest całkowicie poprawna i najbardziej przenośna. Tworzenie aliasingu w stylu C, static_cast lub reinterpret_cast, łamie ścisłe zasady aliasingu (zobacz zrozumienie ścisłego aliasingu). Jeśli skompilujesz z pełną optymalizacją, istnieje duża szansa, że kod nie zrobi dobrze.

Rzucanie przez Unię (taką jak litb ' s my_reint) jest prawdopodobnie najlepszym rozwiązaniem, chociaż technicznie naruszanie zasady, że jeśli piszesz do Związku przez jednego członka i czytasz go przez innego, skutkuje to niezdefiniowanym zachowaniem. Jednak praktycznie wszystkie Kompilatory to wspierają i daje to oczekiwany rezultat. Jeśli bezwzględnie chcesz dostosować się do standardowego 100%, przejdź do metody przesuwania bitów. W przeciwnym razie, polecam iść z casting przez Unii, który prawdopodobnie daje lepsze wyniki.

 13
Author: Adam Rosenfield,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-02 02:03:49

Ignorując efektywność, dla prostoty kodu zrobiłbym:

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

Podoba mi się również ostatnia odpowiedź litba, ta, która zmienia każdy znak po kolei, z tym, że skoro znak może być podpisany, myślę, że potrzebuje dodatkowej maski: {]}

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

Gdy typowanie jest potencjalnym problemem, wolę nie pisać Kalambury, niż próbować robić to bezpiecznie. Jeśli nie tworzysz aliasowanych wskaźników różnych typów, nie musisz się martwić, co kompilator może zrobić z aliasami i ani programista konserwacyjny, który widzi twoje wiele static_casts za pośrednictwem Unii.

Jeśli nie chcesz przydzielić tyle dodatkowej pamięci, to:

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

Wystarczająca optymalizacja pozbędzie się memcpy i dodatkowej zmiennej uint32_t w całości na gcc i po prostu odczyta wartość całkowitą bez wyrównania, w dowolny najbardziej efektywny sposób na twojej platformie, prosto z tablicy źródłowej. Mam nadzieję, że to samo dotyczy innych "poważnych" kompilatorów. Ale ten kod jest teraz większy niż litb, więc nie ma o nim wiele do powiedzenia poza moim, łatwiej jest przekształcić go w szablon funkcji, który będzie działał równie dobrze z uint64_t, a mój działa jako natywny endian - ness, zamiast wybierać little-endian.

To oczywiście nie jest całkowicie przenośne. Zakłada ona, że reprezentacja storage znaków sizeof(uint32_t) odpowiada reprezentacji storage uin32_t w taki sposób, w jaki chcemy. Wynika to z pytania, ponieważ stwierdza, że można być "traktowani jak" inni. Endian-ness, czy znak ma 8 bitów i czy uint32_t używa wszystkich bitów w swojej reprezentacji pamięci, może oczywiście przeszkadzać, ale pytanie sugeruje, że nie.
 6
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-07 14:28:16

Są moje pięćdziesiąt centów-różne sposoby na to.

#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";
}
 1
Author: Петър Петров,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-29 00:58:05

Wiem, że ten wątek jest nieaktywny od jakiegoś czasu, ale pomyślałem, że wrzucę prosty ogólny schemat castingu do tego typu rzeczy:

// 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 );
Mam nadzieję, że to komuś pomoże!
 -3
Author: Hybrid,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-12 12:48:48

To wygląda jak przykład z podręcznika przypadków, kiedy używać reinterpret_cast, cokolwiek innego da ci ten sam efekt bez jasności, jaką uzyskasz dzięki użyciu konstrukcji języka do jego oficjalnego użycia.

 -4
Author: Motti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-07 08:13:36