Forum » Programiranje » Sprememba tipa kazalca [C/C++]
Sprememba tipa kazalca [C/C++]
marjan_h ::
Sem omenil v prejšnji temi, da bom vprašal glede še spremembe tipa kazalca, program je preprost:
Napisan je sicer v C++, vendar v C-ju bi lahko naredili podobno. Kaj pomeni številka ki jo vrne zadnja vrstica: "1076719780"?
V bistvu, je to del nekega znanega algoritma (uporablja se tudi drugje).
float num = 2.71; std::cout << &num << std::endl; std::cout << (int *) &num << std::endl; std::cout << * (int *) &num << std::endl;
Napisan je sicer v C++, vendar v C-ju bi lahko naredili podobno. Kaj pomeni številka ki jo vrne zadnja vrstica: "1076719780"?
V bistvu, je to del nekega znanega algoritma (uporablja se tudi drugje).
phantom ::
Samo to je drugače popolnoma napačen program. Nedefinirano obnašanje. C in C++ predpostavljata, da nekompatibilni pointerji vedno kažejo na različne lokacije v spominu. Aliasata se edino char* in void* s poljubnimi ostalimi. Za takšne zadeve uporabljaj union.
https://gist.github.com/shafik/848ae25e...
Kot drugo, C-jevski casti so velika error-prone packarija in jih ne uporabljat v C++, imaš boljše alternative: static_cast, const_cast, reinterpret_cast in dynamic_cast, pa seveda function cast.
https://gist.github.com/shafik/848ae25e...
Kot drugo, C-jevski casti so velika error-prone packarija in jih ne uporabljat v C++, imaš boljše alternative: static_cast, const_cast, reinterpret_cast in dynamic_cast, pa seveda function cast.
~
~
:wq
~
:wq
Zimonem ::
Seveda je napače. Samo do tega pride v jezikih, ki to dovolijo. V bistvu je primer prav dober. Zato tudi pravim , naučite se c, namesto c++ pa 4aje investirajte čas v Rust.
Zgodovina sprememb…
- spremenilo: Zimonem ()
Vesoljc ::
pointer kot ime implicira je kazalec na koscek pomnilnika, oz. naslov tega koscka pomnilnika. kaj se tam nahaja, pointer sam ne ve. mi predpostavljamo (z deklaracijo), da je to pointer ki kaze na dolocen tip (recimo float). se pravi, vrednost pointerja vsebuje naslov pomnilnika, kjer mi mislimo da se nahajo vsaj 4 byte-i, kateri naj bi predstavljali vrednost tipa float. ko dereferenciramo pointer, se odpravimo na ta naslov, vzamemo 4 byte in jih interpretiramo kot float, izpisemo 2.70999999 ;)
v biti pa nam v c/c++ noben ne brani da ta koscek pomnilnika (4byte) interpretiramo(beremo!) po svoje, ce to zelimo. lahko vzamem samo prvi byte in ga interpretiram kot char/byte ali pa vse 4 in jih interpretiram kot int. jezik (kot neko orodje) to dovoli, na tebi pa je ali bos to uporabljal ali ne. ce imas smiselno uporabo, zakaj ne?
@marjan_h
se direkten odgovor na tvoje vprasanje:
4 byte-i tipa float predstavljajo poseben zapis stevil z plavajaco vejico (IEEE 754) @ Wikipedia, biti so razdeljeni na tri dele, kjer ima vsak del svoj pomen.
tip int je bolj preprost saj predstavlja celo stevilo, kjer vsak zaporedni bit predstavlja vecjo vrednost. ko "tro-delni" float spremenis v int dobis cudno/veliko stevilko :)
v biti pa nam v c/c++ noben ne brani da ta koscek pomnilnika (4byte) interpretiramo(beremo!) po svoje, ce to zelimo. lahko vzamem samo prvi byte in ga interpretiram kot char/byte ali pa vse 4 in jih interpretiram kot int. jezik (kot neko orodje) to dovoli, na tebi pa je ali bos to uporabljal ali ne. ce imas smiselno uporabo, zakaj ne?
@marjan_h
se direkten odgovor na tvoje vprasanje:
4 byte-i tipa float predstavljajo poseben zapis stevil z plavajaco vejico (IEEE 754) @ Wikipedia, biti so razdeljeni na tri dele, kjer ima vsak del svoj pomen.
tip int je bolj preprost saj predstavlja celo stevilo, kjer vsak zaporedni bit predstavlja vecjo vrednost. ko "tro-delni" float spremenis v int dobis cudno/veliko stevilko :)
Abnormal behavior of abnormal brain makes me normal...
Zgodovina sprememb…
- spremenil: Vesoljc ()
kuall ::
lažje je, če si predstavljaš spomin kot zaporedje bitov ali kock in vsaka od teh kock ima svoj naslov, ki je pač neka številka. to je vse. potem pa c/c++ čara s tistimi * in & znaki, da je sama zmeda, znaka pomenita različne stvari v različnih kontekstih. bolje bi bilo, če bi bile funkcije namesto njiju, bi bilo bolj jasno.
če bi izpisal bite (ničle in enke) za int 1076719780 in float 2.71 bi videl, da so biti enaki pri obeh.
float in int sta oba velika 4 bajtov oz. 32 bitov.
output:
sicer v c++ nisem programiral že 20 let po moje. sem vesel, da mi ni treba, ni fajn jezik.
če bi izpisal bite (ničle in enke) za int 1076719780 in float 2.71 bi videl, da so biti enaki pri obeh.
float in int sta oba velika 4 bajtov oz. 32 bitov.
#include <iostream> #include <bitset> void out_float(float f) { size_t size = sizeof(f); unsigned char * p = (unsigned char *)&f; p += size - 1; while (size--) { int n; for (n = 0; n < 8; n++) { putchar('0' + (*p & 128 ? 1 : 0)); *p <<= 1; } p--; } } int main() { // pokažimo bite za int-om std::bitset<32> b(1076719780); std::cout << "int bits:"<< std::endl; std::cout << b << std::endl; // pokažimo bite za float-om std::cout << "float bits:" << std::endl; out_float(2.71); }
output:
int bits: 01000000001011010111000010100100 float bits: 01000000001011010111000010100100
sicer v c++ nisem programiral že 20 let po moje. sem vesel, da mi ni treba, ni fajn jezik.
marjan_h ::
Spet je Vesoljc zelo dobro razložil kaj točno dobim. Kuall je to potrdil s programom. Odlično. :)
V bistvu, kot je phantom napisal je res nedefinirano obnašanje. Temu se v angl. reče type punning (vendar ne vem kako bi to prevedel). Kar dobim je neuporabno, če bi pustili tako, je res?
Vendar so v zgodovini razvoja nekaterih pogonov, uporabili to v svoj prid. Ve kdo o čem govorim? Zelo težko je ta algoritem razumeti.
V bistvu, kot je phantom napisal je res nedefinirano obnašanje. Temu se v angl. reče type punning (vendar ne vem kako bi to prevedel). Kar dobim je neuporabno, če bi pustili tako, je res?
Vendar so v zgodovini razvoja nekaterih pogonov, uporabili to v svoj prid. Ve kdo o čem govorim? Zelo težko je ta algoritem razumeti.
DamijanD ::
Če točno veš kaj delaš, je mogoče direktna operacija na bitku (ali 4ih bajtih - int) hitrejša od neke float matematične inštrukcije. Na podoben način recimo deluje fast inverse square root, ki mislim da je bil v quake igrici in je bil zelo pomemben za hitrost.
mn ::
Spet je Vesoljc zelo dobro razložil kaj točno dobim. Kuall je to potrdil s programom. Odlično. :)
V bistvu, kot je phantom napisal je res nedefinirano obnašanje. Temu se v angl. reče type punning (vendar ne vem kako bi to prevedel). Kar dobim je neuporabno, če bi pustili tako, je res?
Vendar so v zgodovini razvoja nekaterih pogonov, uporabili to v svoj prid. Ve kdo o čem govorim? Zelo težko je ta algoritem razumeti.
OK, če je znan algoritem zakaj ne napišeš kateri? Ker tisti code chunk, ki si ga ti napisal ne razumem kako bi bil uporaben za karkoli.
Sem spregledal post od phantoma. Torej če prav razumem takšna koda nek osnovni "assumption" od prevajalnika ne upošteva, in posledično reorganizacija kode, ki jo optimizer lahko izvede ni več nujno pravilna.
Zgodovina sprememb…
- spremenilo: mn ()
GupeM ::
OK, če je znan algoritem zakaj ne napišeš kateri? Ker tisti code chunk, ki si ga ti napisal ne razumem kako bi bil uporaben za karkoli.
Ravno tako ne vem zakaj bi bilo to nedefinirano obnašanje?
Gre za inverse square root algoritem iz Quake 3, kjer so uporabili konverzijo iz float v long in nato nazaj v float. S tem so sicer dobili približno vrednost, ne točne, ampak je zadeva za gaming pač dovolj točna.
GupeM ::
phantom ::
Hehe torej sem imel prav
Tudi jaz sem pomislil na ta algoritem.
Problem aliasinga je v tem, da compiler privzame, da pointerja float* in int* ne kažeta na isto lokacijo. Torej lahko v kodi:
float f = 42.0f; int i = *(int*)&f;
preuredi zaporednje dostopov do polnilnika, torej najprej prebere i in šele nato zapiše f. Kar seveda pomeni, da v i dobimo neko random število. To se sicer zgodi redko in zgolj z optimizacijami in takrat je ponavadi napako zelo težko najdi. V eni firmi, ker sem delal, so baje imeli konkretno štalo glede tega in so rabili precej časa (teden?) preden so našli ta bug. To se jim je zgodilo samo na GCC-ju, na PS3 portu njihovega engina. In potem je njihov TCO uporabljal to kot argument, da je GCC mofo ghetto crap od compilerja
Pravilno je to narediti preko uniona, tako compilerju damo eksplicitno vedeti, da se sta spremenljivki aliasa do iste lokacije v pomnilniku:
union FloatToInt { float f; int i; }; int i = FloatToInt{42.0f}.i;
~
~
:wq
~
:wq
Ahim ::
Hehe torej sem imel prav
Ne vem. Bo OP povedal. Jaz samo sklepam, da gre za ta algoritem, ki je verjetno najbolj znan algoritem s takšnim trikom. Ali pa edini?
Ni tako zelo glamurozno kakor (priblizno) racunanje fp stevil, ampak pri premetavanju podatkov imas nebroj bufferjev kasneje mapiranih na razlicne strukture, na katerih se potem vrsijo operacije (npr. racunajo checksumi, nastavljajo flagi itd.), ceprav je celoten buffer na zacetku samo brezoblicno zaporedje bajtov. Recimo embedded networking, kjer brez kopiranja opravljas operacije nad paketki na vecih nivojih stacka.
Irbis ::
In pri teh strukturah je potem dobro pomisliti še na kakšen #pragma pack, ker se ti znotraj structa lahko hitro zgodi, da ti prevajalnik nameče kakšne dodatne praznine (sploh če stvari prenašaš med različnimi sistemi). Tukaj je že ena stvar, zakaj je kdaj vseeno varneje dostopati s posamičnimi kazalci namesto s structom/unionom.
Zimonem ::
Undefined behaviourjem , se je vseeno najbolje izogniti. Sploh če je več ljudi na projektu.
Ahim ::
In pri teh strukturah je potem dobro pomisliti še na kakšen #pragma pack, ker se ti znotraj structa lahko hitro zgodi, da ti prevajalnik nameče kakšne dodatne praznine (sploh če stvari prenašaš med različnimi sistemi). Tukaj je že ena stvar, zakaj je kdaj vseeno varneje dostopati s posamičnimi kazalci namesto s structom/unionom.
Vsekakor packed, ne more kar prevajalnik nekaj dodajati po svoje ce si ti programer ... dela tako kot mu reces
Saj dejansko ne namece nekaj vmes, ker bi mu to randomly pasalo, ampak poravna premajhne spremenljivke na boundary dostopa do RAMa (odvise od arhitekture, za katero pises program), posledica so pa pac praznine. V glavnem: packed.