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ł?
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.
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.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";
}
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!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.
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