Avatar
coells
Redaktor
Avatar
coells:

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):

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
:)
Avatar
Odpovídá na coells
Libor Šimo (libcosenior):

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):
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 (Luckin):

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 (Luckin)
Libor Šimo (libcosenior):

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):

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
Odpovídá na Libor Šimo (libcosenior)
Lukáš Hruda (Luckin):

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  +1 30.11.2014 13:46
Avatar
coells
Redaktor
Avatar
Odpovídá na Luboš Běhounek (Satik)
coells:

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
Redaktor
Avatar
Odpovídá na Lukáš Hruda (Luckin)
coells:

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

 
Nahoru Odpovědět 30.11.2014 13:56
Avatar
coells
Redaktor
Avatar
coells:

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
Odpovídá na coells
Lukáš Hruda (Luckin):

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
Redaktor
Avatar
Odpovídá na Lukáš Hruda (Luckin)
coells:

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

 
Nahoru Odpovědět 30.11.2014 14:10
Avatar
Odpovídá na coells
Lukáš Hruda (Luckin):

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):

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
Odpovídá na coells
Lukáš Hruda (Luckin):

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
Redaktor
Avatar
Odpovídá na Lukáš Hruda (Luckin)
coells:

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
Odpovídá na coells
Lukáš Hruda (Luckin):

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í
+1 bodů
Řešení problému
 
Nahoru Odpovědět 30.11.2014 15:10
Avatar
Nahoru Odpovědět 30.11.2014 15:26
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
Nahoru Odpovědět 30.11.2014 15:31
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
coells
Redaktor
Avatar
Odpovídá na Lukáš Hruda (Luckin)
coells:

... 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
Odpovídá na coells
Lukáš Hruda (Luckin):

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
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:

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
Redaktor
Avatar
Odpovídá na Lukáš Hruda (Luckin)
coells:

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):
  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):

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
:)
Avatar
Odpovídá na coells
Lukáš Hruda (Luckin):

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
Redaktor
Avatar
Odpovídá na Libor Šimo (libcosenior)
coells:

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):

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
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.