Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
Avatar
coells
Tvůrce
Avatar
coells:30.11.2014 3:05

Malé puzzle pro programátory v C!

Tenhle malý prográmek

int main(int argc, const char **argv)
{
    int *a = (int *)malloc(sizeof(int));
    int *b = (int *)realloc(a, sizeof(int));
    *a = 0;
    *b = 1;
    if (a == b)
        printf("a=%p b=%p => *a=%d *b=%d\n", a, b, *a, *b);

    return 0;
}

Vypíše

a=0x1001054e0 b=0x1001054e0 => *a=0 *b=1
Program ended with exit code: 0

Tři otázky:

  1. jak se mi to povedlo?
  2. jak je to možné?
  3. kde je v programu chyba?

Použijte kompilátory llvm nebo MS Visual C++, čisté gcc jsem nezkoušel.

 
Odpovědět
30.11.2014 3:05
Avatar
Luboš Běhounek Satik:30.11.2014 8:23

http://imgs.xkcd.com/comics/cnr.png

:(

http://ctrlv.cz/19Xr

(MSVS 2010 a 2013, zkoušeno jako C i C++, DEBUG i RELEASE)

Nahoru Odpovědět
30.11.2014 8:23
https://www.facebook.com/peasantsandcastles/
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):30.11.2014 8:34

Najprv som to testol na Code:Blocks a pracovalo to presne ako píšeš, ale potom som zmenil hodnoty v *a *b na 9-miestne čísla a už to vypisovalo len číslo v *b.
Potom som schválne nainštaloval MS Visual C++ (express 2010) a tam to hneď vypisovalo hodnoty z *b.

  1. jak se mi to povedlo?

oklamal si compilátor

  1. jak je to možné?

neviem

  1. kde je v programu chyba?

int *b = (int *)realloc(a, sizeof(int));
pretože funkcia realloc() sa pokúša zmeniť veľkosť už vopred alokovanej pamäti pomocou funkcie malloc alebo calloc, čo v tomto prípade nie je splnené.

Nahoru Odpovědět
30.11.2014 8:34
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Libor Šimo (libcosenior):30.11.2014 8:48
int main(int argc, const char **argv)
{
    int *a = (int *)malloc(sizeof(int)); // vyčlenená pamäť pre 1 int
    // *b nebol alokovaný
    int *b = (int *)realloc(a, sizeof(int)); // pointer *b = pointer *a
    *a = 0;
    *b = 1; // obsah *b = obsah *a
    if (a == b)
        printf("a=%p b=%p => *a=%d *b=%d\n", a, b, *a, *b);

    return 0;
}
Nahoru Odpovědět
30.11.2014 8:48
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Lukáš Hruda
Tvůrce
Avatar
Lukáš Hruda:30.11.2014 12:01

Zkoušel jsem Code::Blocks, Dev-Cpp a VS2013, ve všech třech případech jsem dostal tohle:

a=004776B0 b=004776B0 => *a=1 *b=1

Tipnul bych si, že výše popsaný jev je způsoben nějakou optimalizací kompilátoru spojenou s cachováním paměti.

 
Nahoru Odpovědět
30.11.2014 12:01
Avatar
Odpovídá na Lukáš Hruda
Libor Šimo (libcosenior):30.11.2014 12:26

Ja si myslím, že je to chybou compilatoru. V mojom prípade najskôr pracoval ako písal coels, ale po zmene obsahu pointer sa optimalizovať. Asi to bolo tým, že *a = 0

Nahoru Odpovědět
30.11.2014 12:26
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Odpovídá na Libor Šimo (libcosenior)
Libor Šimo (libcosenior):30.11.2014 12:51

Navyše hodnota 0 je asi NULL, preto je asi možné že bol compilátor oklamaný.

Nahoru Odpovědět
30.11.2014 12:51
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Lukáš Hruda
Tvůrce
Avatar
Odpovídá na Libor Šimo (libcosenior)
Lukáš Hruda:30.11.2014 13:46

Tím to není, NULL je normální nula pouze přetypovaná na pointer a z výpisu je zřejmé, že pointer a hodnotu NULL nemá.

 
Nahoru Odpovědět
30.11.2014 13:46
Avatar
coells
Tvůrce
Avatar
Odpovídá na Luboš Běhounek Satik
coells:30.11.2014 13:53

Proto je to puzzle :-)

Zkus to ve Visual Studiu spustit s malou úpravou (VS2010 i VS2012)

int main(int argc, const char **argv)
{
    int *a = (int *)malloc(sizeof(int));
    int *b = (int *)realloc(a, sizeof(int));
    *a = 0;
    *b = 1;
    if (a == b)
        printf("*a=%d *b=%d\n", *a, *b);

    return 0;
}
 
Nahoru Odpovědět
30.11.2014 13:53
Avatar
coells
Tvůrce
Avatar
Odpovídá na Lukáš Hruda
coells:30.11.2014 13:56

To je pravda, jakou hodnotu má ale pointer <a>?

 
Nahoru Odpovědět
30.11.2014 13:56
Avatar
coells
Tvůrce
Avatar
coells:30.11.2014 14:05

Trochu se divím, že nikdo nenašel v programu chybu, na to si ho nemusíte ani spouštět.

Libor Šimo (libcosenior) tvoje analýza programu není správně

 
Nahoru Odpovědět
30.11.2014 14:05
Avatar
Lukáš Hruda
Tvůrce
Avatar
Odpovídá na coells
Lukáš Hruda:30.11.2014 14:07

Podle výpisu má stejnou hodnotu jako pointer b, což je logické i proto, že funkce realloc v případě "nezvětšení bloku paměti" data nepřesouvá, mělo by tedy platit že a == b, což zjevně platí, protože jinak by ta podmínka ani nebyla splněna a nic by se nevypsalo.

Editováno 30.11.2014 14:07
 
Nahoru Odpovědět
30.11.2014 14:07
Avatar
coells
Tvůrce
Avatar
Odpovídá na Lukáš Hruda
coells:30.11.2014 14:10

:-) Opravdu? Můžeš tohle tvrzení něčím podložitm, například dokumentací?

 
Nahoru Odpovědět
30.11.2014 14:10
Avatar
Lukáš Hruda
Tvůrce
Avatar
Odpovídá na coells
Lukáš Hruda:30.11.2014 14:17

V dokumentaci je napsáno, že data můžou nebo nemusí být přesunuta. Přičemž mi přijde logické, že pokud nový blok má stejnou velikost jako ten původní, data se nepřesouvají, protože k tomu není důvod. Pokud by se data přesunula, pak by nemohlo platit, že a == b a nic by se nevypsalo.

 
Nahoru Odpovědět
30.11.2014 14:17
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):30.11.2014 14:42

Tá malá zmena ale vlastne nič nerobí a výpis je rovnaký, nie ako tvoj.

Nahoru Odpovědět
30.11.2014 14:42
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Lukáš Hruda
Tvůrce
Avatar
Odpovídá na coells
Lukáš Hruda:30.11.2014 15:00

Po té změně jsem ve VS již dostal stejný výsledek, ale pouze v release módu. Z toho soudím, že je to opravdu způsobeno optimalizací kompilátoru, kde při přiřazení *b = 1 pravděpodobně program přepisuje data někde v cache a ne přímo v paměti na kterou pointer ukazuje. Pak při výpisu přes pointer b program přistupuje do cache ale přes pointer a přistupuje do paměti na adresu, na kterou ukazuje.

Editováno 30.11.2014 15:02
 
Nahoru Odpovědět
30.11.2014 15:00
Avatar
coells
Tvůrce
Avatar
Odpovídá na Lukáš Hruda
coells:30.11.2014 15:05

Takže jsi přišel na otázku (a), ale co otázka (c)?

Opravdu nikdo nevidí tu chybu v programu?

 
Nahoru Odpovědět
30.11.2014 15:05
Avatar
Lukáš Hruda
Tvůrce
Avatar
Odpovídá na coells
Lukáš Hruda:30.11.2014 15:10

Chyba je imho v tom, že přiřazujeme data tam, kam ukazuje pointer a, i přesto, že paměť, na kterou ukazuje byla realokována, tedy obecně již může být uvolněná a nemělo by se k ní takto přistupvat. Kompilátor s tím očividně při optimalizacích počítá a proto nastal takovýto zvláští jev.

Akceptované řešení
+20 Zkušeností
+2,50 Kč
Řešení problému
 
Nahoru Odpovědět
30.11.2014 15:10
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):30.11.2014 15:26

Myslíš uvoľnenie pamäti?

Nahoru Odpovědět
30.11.2014 15:26
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Libor Šimo (libcosenior):30.11.2014 15:31

free(a);

Nahoru Odpovědět
30.11.2014 15:31
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
coells
Tvůrce
Avatar
Odpovídá na Lukáš Hruda
coells:30.11.2014 15:37

... což vede optimalizátor k tomu, že 'a' a 'b' nemají nic společného a přesun instrukcí provede nezávisle. ]:>
Jedna z variant, co vidí kompilátor.

a = (int *)malloc(sizeof(int))
b = (int *)realloc(a, sizeof(int));
/* a = undefined */

/* assign to undef, optimize value */ *a = 0;
*b = 1;
if (a == b)
    printf("*a=%d *b=%d\n", 0, 1); /* a, b independent */

Důkaz, že to tak je, je jednoduchý, s malou úpravou už bude výstup zcela korektní:

a = (int *)malloc(sizeof(int))
b = (int *)realloc(a, sizeof(int));
a = b
// ...
Editováno 30.11.2014 15:37
 
Nahoru Odpovědět
30.11.2014 15:37
Avatar
Lukáš Hruda
Tvůrce
Avatar
Odpovídá na coells
Lukáš Hruda:30.11.2014 15:46

Chtělo by to víc takových programátorských hříček, člověk se alespoň odreaguje a zapřemýšlí nad něčím zajímavým. :)

 
Nahoru Odpovědět
30.11.2014 15:46
Avatar
coells
Tvůrce
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:30.11.2014 15:48

Libco, alokace paměti v C je plná triků:

int *a, *b;
a = (int *)malloc(sizeof(int));
b = (int *)realloc(a, sizeof(int));
  1. jaké jsou hodnoty proměnných ještě před zavoláním malloc()?
  2. kolik paměti v bytech se alokovalo pomocí malloc()?
  3. kolik paměti v bytech se alokovalo pomocí realloc()?
  4. jaké jsou hodnoty proměnných na konci bloku?

Zkus si odpovědět podle specifikace C bez použití debuggeru.

 
Nahoru Odpovědět
30.11.2014 15:48
Avatar
coells
Tvůrce
Avatar
Odpovídá na Lukáš Hruda
coells:30.11.2014 15:57

A hlavně se poučí, že logické předpoklady jsou sice nutné, ale s referenční dokumentací mají málo společného. :-D

Včera jsem hodinu honil jeden pointer, který způsoboval segfault na místě, kde se nic nedělo.
Nakonec se ukázalo, že mi tam kompilátor z definice typu proměné cpe free() navíc, o který jsem nestál a uvolňuje pamět, která není moje.

 
Nahoru Odpovědět
30.11.2014 15:57
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):30.11.2014 16:01
  1. jaké jsou hodnoty proměnných ještě před zavoláním malloc()?

tie, ktoré tam boli pred alokáciou

  1. kolik paměti v bytech se alokovalo pomocí malloc()?

4 bajty

  1. kolik paměti v bytech se alokovalo pomocí realloc()?

4 bajty

  1. jaké jsou hodnoty proměnných na konci bloku?

tie, ktoré tam boli pred alokáciou

Nahoru Odpovědět
30.11.2014 16:01
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Odpovídá na coells
Luboš Běhounek Satik:30.11.2014 16:04

Release v 2012 už fungoval.

Koukal jsem na to v asm a je to jak píšeš - kompilátor tam do výpisu rovnou posílal hodnoty 0 a 1. :)

Nahoru Odpovědět
30.11.2014 16:04
https://www.facebook.com/peasantsandcastles/
Avatar
Lukáš Hruda
Tvůrce
Avatar
Odpovídá na coells
Lukáš Hruda:30.11.2014 16:08

Já nedávno narazil na naprosto nepochopitelnou chybu v gcc. Měl jsem funkci, která přebírala pointer typu void*. Do dat předaných pointerem nic nezapisovala, pouze z nich četla. Tu funkci jsem zavolal v jiném souboru a předával jí adresu floatové proměnné. K mému údivu, se tato proměnná z naprosto nepochopitelných důvodů vždy vynulovala (i přesto, že funkce na tu adresu nic nezapisovala). Dlouho jsem hledal chybu, až jsem přišel na to, že jsem zapomněl naincludovat header, který danou funkci obsahoval. Doteď absolutně nechápu, jak je možné, že kompilátor nehlásil chybu a že tu funkci dokázal zavolat i přesto, že v daném souboru o ní nemohl vědět, ale dělala očividně něco uplně jiného než měla. Po naincludování příslušného souboru chyba zmizela.

 
Nahoru Odpovědět
30.11.2014 16:08
Avatar
coells
Tvůrce
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:30.11.2014 16:24

Všechny odpovědi jsou, bohužel, špatně. Takhle C opravdu nefunguje.

 
Nahoru Odpovědět
30.11.2014 16:24
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):30.11.2014 16:26

Fajn, ako to teda je?
namiesto 4 bašty som mal napísať sizeof(int)?

Editováno 30.11.2014 16:29
Nahoru Odpovědět
30.11.2014 16:26
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Libor Šimo (libcosenior):30.11.2014 16:29

bajty

Nahoru Odpovědět
30.11.2014 16:29
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 30 zpráv z 30.