Kontrola správnosti použití paměti
                     -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

          Price Wang's programmer was coding software. His fingers  danced
      upon the keyboard. The program  compiled  without an error  message,
      and the program ran like a gentle wind.
          Excellent!" the Price exclaimed, "Your technique is faultless!"
          "Technique?"  said the programmer,  turning  from his  terminal,
      "What I follow  is the  Tao -- beyond  all  technique.  When I first
      began to program I would  see  before me the  whole  program  in one
      mass.  After  three  years  I no  longer  saw  this  mass.  Instead,
      I used  subroutines.  But now I see nothing. My whole  being  exists
      in a formless  void. My  senses  are  idle. My spirit,  free to work
      without a plan,  follows  its  own  instinct.  In short,  my program
      writes  itself. True, sometimes there are difficult  problems. I see
      them coming, I slow down, I watch  silently.  Then I change a single
      line of code and the difficulties  vanish  like puffs of idle smoke.
      I then  compile the program. I sit still and let the joy of the work
      fill my being. I close my eyes for a moment and then log off."
          Price  Wang  said,  "Would  that  all of my programmers  were as
      wise!"
                  -- Geoffrey James, "The Tao of Programming"


          Chyby při použití dynamicky alokované paměti jsou ty nejčastější
      a nejzáludnější.  Zejména  proto, že se často nemusí vůbec  projevit
      a  teprve  časem  vyústit  v  katastrofu.  V C je  situace  obvzlášť
      komplikovaná, protože jeho hlavní zbraň (ukazatele) k takovým chybám
      přímo  svádí. U jazyků  jako je Lisp, kde  ukazatel je věc  neznámou
      a nad celou  pamětí  vládne  všemocný  garbage  collector je situace
      mnohem  přívětivější.  Kupodivu ale i tam jsou podobné chyby poměrně
      časté.

          Právě pro záludnost  odhalování těchto chyb bylo vymyšleno velké
      množství šikovných  utilitek, knihoven a jiných udělátek, které mají
      za úkol podobné potíže odhalit. Protože jich existuje poměrně hodně,
      rozhodl  jsem  se  po  nich  porozhlédnout a otestovat  je.  Každému
      doporučuju, aby si své programy jednou za čas zkontrolovat  podobnou
      utilitkou, proto6e ušetří to hodně starostí jak jemu, tak uživatelům
      programu.

          Napřed je ale nutné zjistit, o jaké chyby se vlastně  jedná. Pro
      většinu těchto chyb jsem vytvořil  testovací  prográmky a jednotlivé
      utility  pak  podrobím  těmto  testům.  Chyby lze v zásadě  rozdělit
      do dvou hlavních skupin.

                           Chyby při použití haldy:

          Do této kategorie patří zejména  vícenásobné  uvolňování  bloků,
      uvolnění  už  uvolněných   bloků  či  vůbec  nenaalokovaných   bloků
      a alokace nebo  realokace  bloků  velikosti 0 (zde standardy  nemají
      příliš jasno, jak se program má zachovat).

          Tyto  chyby  chytá  glibc  už sama o sobě  relativně  spolehlivě
      (narozdíl od libc v jiných operačních systémech). Existuje i několik
      robusnějších  knihoven  (efence,  dbmalloc, mpr, ccalloc),  které se
      na hledání  podobných chyb specializují.  Snad všechny tyto knihovny
      takové  chyby  chytí,  pokud samozřejmě se nenachází v nějaké  části
      kódu, která se provádí jenom zřídka. To je důvod, proč by člověk měl
      používat tyto knihovny při vývoji pravidelně.

          Kapitola sama pro sebe je neuvolňování bloků, což sice nezpůsobí
      pád programu, ale je trestuhodným plýtváním pamětí. Zejména netscape
      je tím proslulé.  Některé  knihovny pro kontrolu  paměti  (ccmalloc)
      také nabízí  vypsání  všech  neuvolněných  bloků na konci  programu.
      Pokud se člověk donutí k tomu, aby všechny bloky pečlivě  uvolňoval,
      je to asi nejspolehlivější prostředek, jak se vyvarovat těchto chyb.

                         Chyby při použití ukazatelů:

          Zde  se  jedná  hlavně o zápis  a  čtení  mimo  alokovaná  místa
      v  paměti.   ANSI C  navíc   ale   zakazuje  i  práci   (aritmetiku)
      s ukazately,  které  ukazují mimo  alokované  oblasti. To je poněkud
      problematické,  protože  takové  situace   vznikají  poměrně  často.
      Proto ANSI C z tohoto pravidla  vyjíma tak zvanou "NULL zone". Navíc
      povoluje práci s ukazatelem  ukazujícím jednu položku za konec pole,
      protože  tato situace  vzniká  často u cyklů, kde ukazatel  projíždí
      celé pole.  Dodržování  tohoto  standardu je poněkud  složitější, už
      jenom proto, že mnoho  běžných  konstrukcí (jako vaargs) potom nelze
      korektně použít. Navíc se takové chyby jen velmi těžko stestují.

          Přístup za a před  pole jsou asi nejčastější.  Mohou se stát jak
      na haldě, tak na zásobníku (zde pak vznikají známé  buffer  owerflow
      problémy)  tak  u  inicializovaných  dat.  Přitom   běžné  kontrolní
      knihovny  většinou mají šanci odchytit pouze ty první. Nejsložitější
      je asi situace u inicializovaných dat, protože zde dokonce  existují
      programy, co podobné věci dělají úmyslně.

          Dalším  oblíbeným  problémem  je používání už  uvolněných  bloků
      a zápis do read-only stringů. Pokud provedete v c konstrukci:

      char *c="ahoj";

          string je read  only,  narozdíl od polí. V operačních  systémech
      bez  ochrany  paměti  zápis  projde a člověk se pak v operavdovém OS
      nestačí divit. Je třeba ještě podotknout, že string vytvořený pomocí
      konstrukce:

      char c[]="ahoj";

          už  readonly  není,  protože  se  jedná  o  inicializaci   pole.
      Nejsem si ale jist,  jestli to není GNU extension,  protože  některé
      překladače to nemají rádi.

          Časté potíže také způsobuje  používání  neinicializované paměti.
      Programy  často  předpokládájí, že paměť je snulována. To ale nemusí
      být pravda a programy se potom chovají podivně.

          Nejčastější   problémy  jsem  shrnul  do  několika   testovacích
      prográmků a zjistil, jak vypadá situace na glibc:

Chyby pro použití haldy
  zapomenuté naalokované bloky paměti
  uvolnění nenaalokovaného bloku               crash v __libc_free
  vícenásobné uvolnění  bloku                  crash v __libc_free
  alokace a realokace bloku o velikosti 0      alokuje a realokuje normálně
  použití paměti bez testu na selhání malloc
Chyby pro použití ukazatelů
  použití uvolněného bloku pro zápis
  použití uvolněného bloku pro čtení           načte starý obsah
  zápis za koncem pole v zásobníku
  zápis za koncem alokovaného bloku
  čtení za koncem alokovaného bloku            načte 0
  zápis daleko za koncem alokovaného bloku
  zápis před koncem alokovaného bloku          podivný chrash uvnitř glibc
  čtení před koncem alokovaného bloku          načte 0
  čtení neinicalizované paměti                 načte 0

          U testů, kde není  napsáno nic,  program  proběhl a žádná  chyba
      se neprojevila. Jak vidíte, glibc odhalila  vícenásobné  uvolňování.
      U alokace  velikosti 0 se chovala  také  korektně  podle  jednoho ze
      standardů.  Zápis  před  koncem  bloku  způsobil  zmatek v interních
      strukturách mallocu a pád. Není se ale moc čemu divit. Ostatní chyby
      zůstaly bez povšimnutí.

          Libc v DJGPP (podobně jako i většina ostatních libc) se nevšimla
      vůbec ničeho.

                                    mcheck                                

                                Electric fence                            

                                    lclint                                

                                    Checker                               

                                   CCmalloc                               

                                      mpr                                 

                                     Závěr

          Jedůkladějším a nejchytřejším  programem pro kontrolu paměti byl
      rozhodně  checker. Jeho  nevýhodou je ale velké  zpomalení a nutnost
      uzavřenosti kódu. Takže se nehodí na všechno. U grafických  programů
      většinou  neexistují  wrappery do knihoven a zpomalení je tak velké,
      že jsou programy potom nepoužitelné.

          Pokud  nemůžete  použít  checker,  asi  nejlepší   knihovna  pro
      kontrolu je libefence. Tu lze  používat  běžně při  ladění,  protože
      příliš  nezpomaluje a najde hodně nedostatků.  Jednou za čas je také
      dobré vyzkoušet  kontrolu z druhé strany (PROTECT_BELLOW) a kontrolu
      uvolněných  bloků (PROTECT_FREE). Na hledání  zapomenutých bloků ale
      bohužel  libefence  použít  nejde. Pro to je také dobré  přinutit se
      uvolňovat  opravdu  všechny  bloky (a nepočítat s tím, že se nakonci
      uvolní samy) a jednou za čas otestovat  ccmalloc.  Zajímavá  utilita
      je pravděpodobně i lclint, který může ušetřit opravdu hodně starostí
      nejen s pamětí ale i s kompatibilitou apod.


            výheň