Audit zdrojového kódu v C (prípadne v C++)

Audit zdrojového kódu v C (prípadne v C++)
« kdy: 05. 11. 2010, 12:22:08 »
Chcem sa opýtať či existuje (poznáte) neaký nástroj ktorý by prezrel zdrojový kód v C/C++ a upozornil na prípadné chyby. Nemyslím lexikálne ani syntaktické ale tie ako zabudnutý break vo switch, nedáná default hodnota, preplnený stack a pod.

Ďakujem za radu.


Augur


Jiri

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #2 kdy: 05. 11. 2010, 13:23:40 »
zapomenutý break ve switch neni chyba

Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #3 kdy: 05. 11. 2010, 13:44:54 »
Zapomenutý break chyba je. To, že někdy je větev bez ÚMYSLNĚ uvedeného breaku je věc druhá. Jelikož je úmyslné neuvedení řidší než opomenutí, má smysl při auditu kódu se na to zaměřit, byť to chce lidskou kontrolu.


PS: V každém případě je switch jedna z chyb v návrhu C/C++, správné by bylo, kdyby se explicitně označovalo pokračování v následující (nebo ještě lépe v dané) větvi a standardně by se to chovalo jako s break.

anonymous

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #4 kdy: 05. 11. 2010, 14:31:28 »
PS: V každém případě je switch jedna z chyb v návrhu C/C++, správné by bylo, kdyby se explicitně označovalo pokračování v následující (nebo ještě lépe v dané) větvi a standardně by se to chovalo jako s break.

to je dosti subjektivni nazor. pokud na c nahlizis jako lowest common denominator assembler (switch/case = ekvivalent jumptable v asm || serie ifu s fallthru), zacinaji veci davat lepsi smysl. asi je dost nestastne ze nekdo povazuje c v dnesni dobe za hll


Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #5 kdy: 05. 11. 2010, 14:42:23 »
Proč je teda v C např. for cyklus? Ten taky nemá obdobu v asm.
C prostě není assembler a je mi úplně jedno, jak se co píše v asm. Obě konstrukce jsou sémanticky ekvivalentní a překlad obou z nich je bezproblémový. Proto je imho chyba, pokud návrháři jazyka zvolili tu, která vede k delšímu, méně čitelnému kódu s větší pravděpodobností chyb.

Radovan

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #6 kdy: 05. 11. 2010, 16:35:59 »
PS: V každém případě je switch jedna z chyb v návrhu C/C++...
A to bych se hádal, protože zrovna tahle věc je v Céčku úplně fantastická. Tvůj názor zřejmě pramení z nepochopení jeho účelu, on totiž ten switch není primárně určený k větvení, na to je tam if/else a dá se s nimi bohatě vystačit, switch je jeden blok příkazů, do kterého je možné vstoupit na libovolném místě! Prostě to samé jako v BASICu byl příkaz ON GOTO. To že z něj jde také libovolně vyskočit pomocí break je jen bonus, proč ho tam nepoužít, když už v tom jazyce je a umí to.

C opravdu není assembler, je to hodně vylepšený přenositelný makroassembler maskovaný za programovací jazyk ;-) A u toho for v assembleru bych si také nebyl tak jistý, třeba mikroprocesor Z80 má instrukci djnz: http://z80-heaven.wikidot.com/instructions-set:djnz
Samozřejmě for v C umí mnohem víc věcí než jen počítat cykly, stejně tak jako switch se dá použít několika úplně odlišnými způsoby, a u dalších příkazů je to podobné.

Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #7 kdy: 05. 11. 2010, 19:09:03 »
Citace
A to bych se hádal, protože zrovna tahle věc je v Céčku úplně fantastická
Jenže vede k chybám. Samozřejmě já netvrdím, že by se měla zcela vypustit. Ale jelikož je standardní (v 90% případů se to tak používá) používat bloky samostatně, tak by měla být defaultní možnost skočit ven, zatímco možnost pokračovat dalším (nebo i libovolným - i to by se často hodilo a nejde to - teda bez goto) blokem by měla být označena nějakým klíčovým slovem.

Citace
switch není primárně určený k větvení, na to je tam if/else a dá se s nimi bohatě vystačit,
Ono vystačit si jde i s goto, že... Jde o to, co je čitelnější forma zápisu a tady switch rozhodně vede...
Navíc úplně nedá, protože switch s rozumnými numerickými hodnotami může překladač přeložit "chytřeji" než posloupnost podmínek.

Citace
...jeden blok příkazů, do kterého je možné vstoupit na libovolném místě...
Jo? IMHO switch znamená přepnout či přepínač. Přepnout znamená buď, anebo, XOR. Takže pokud je toto zamýšlené použití (imho nikoli), tak je naprosto špatně zvoleno klíčové slovo. Navíc, jak jsem psal, toto použití je minoritní. Takže buďto autoři C špatně navrhli tuto konstrukci, nebo špatně zvážili četnost užívání jednotlivých konstrukcí. "Dobře" (ve smyslu tak, aby to vedlo ke stručnému, čitelnému a bezchybnému kódu) to ale rozhodně není.
« Poslední změna: 05. 11. 2010, 20:15:21 od Logik »

justik

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #8 kdy: 06. 11. 2010, 11:15:27 »
Pokud by nemuselo byt reseni pouze pro Linux, doporucuji Static Code Analysis tool ve vyssich verzich Visual Studia 2010...  Je zalozeno na FxCop...

Radovan

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #9 kdy: 06. 11. 2010, 11:34:37 »
Jenže vede k chybám.
To že auto může vjet do protisměru také vede k nehodám, a přesto se jízdní pruhy neoddělují betonovou bariérou. Ono se sice v 90% případů jezdí vpravo, ale občas se jiná možnost hodí, třeba při předjíždění nebo objíždění rozkopané silnice. Prostě to řízení tak funguje a je k tomu dobrý důvod, jinak by z toho byla autodráha. Dokonce se tahle vlastnost dá použít i k obracení, parkování a jiným více či méně nebezpečným vylomeninám, ale přitom jde pořád jen o změnu úhlu natočení předních kol ;) Řidič sám musí vědět co dělá, nikdo jiný ho neohlídá.
Citace
switch s rozumnými numerickými hodnotami může překladač přeložit "chytřeji" než posloupnost podmínek.
Rozhodně ano, přinejmenším by ten kousek kódu měl být kratší a rychlejší, pokud tedy bude fungovat jako jeden výběr z tabulky adres místo postupného provádění několika podmíněných skoků. Takže jak jsem psal, switch je zakuklený a vylepšený ekvivalent pozdějšího basicového ON n GOTO label1, label2, label3..., což byl jeden z marných pokusů jak samotné používání GOTO trochu ukáznit a aspoň nějak ten špagetový kód strukturovat. (Ale FORTRAN to měl o "sto" let dříve!)
Citace
IMHO switch znamená přepnout či přepínač.
No však jo, přepínač na některý ze vstupních bodů toho bloku 8) Třeba v QBASICu (ne že by ten byl dobrý příklad) je to takhle:

SELECT CASE výraz
  CASE IS >= 10
    větev 1
  CASE 2 TO 5
    větev 2
  CASE 1
    větev 3
  CASE ELSE
    větev 4
END SELECT

Nikde žádný break, prostě SELECT jako výběr jedné z několika větví (a také žádná jiná možnost, aspoň bez použití GOTO). U switche se sice přesně tenhle případ použití uvádí v učebnicích, ale stejně jako většina věcí v C toho umí mnohem víc. Ještě spíš bych řekl že switch umí něco úplně jiného, ale k tomuhle se dá díky své univerzálnosti použít také.
Řekněme, že v "bezpečných" jazycích (jako je Karel ;D) má programátor jen jednu možnost jak to udělat a co smí udělat, nic jiného mu ten jazyk pro jistotu nedovolí. Tady v C má možností mnohem víc, a proto je také jen na jeho zodpovědnosti, jestli a jak si ohlídá že omylem nepoužil zrovna jinou možnost než chtěl, to není syntaktická chyba, spíš logická, jako kdyby místo A+B napsal A-B. Obojí jde a obojí je v tom jazyce správně, tak proč jedno z nich zakazovat jen proto že se používá méně často a "vede k chybám"? Ne, nevede, jen to prostě udělá něco jiného. (Však to odčítání je vlastně také úplně zbytečné, vždycky můžu použít A+(-B), nebo ne? Unární minus to jistí.)
Tohle prostě není Pascal, kde se padá do peřin a ještě navrch umí pofoukat bebíčko. C je jako břitva, a pokud si někdo při holení uřízne nos, je to jen jeho chyba a buďto si měl dávat víc bacha nebo koupit Braun :o
Citace
Navíc, jak jsem psal, toto použití je minoritní.
Ano, častěji se switch používá tak jako ten SELECT, troufl bych si tvrdit že je dost lidí kteří si ani neuvědomují že to jde i jinak (případně to považují za chybu):

                    |
    ----------------+-----------------
   /              SELECT              \
  --+----------+----------+----------+--
    |          |          |          |
+---+---+  +---+---+  +---+---+  +---+---+
| blok1 |  | blok2 |  | blok3 |  | blok4 |
+---+---+  +---+---+  +---+---+  +---+---+
    |          |          |          |
    +----------+----+<----+<---------+
                    |

Ale to přece není důvod k omezování těch ostatních možností, které tam jsou! Ono to totiž ve skutečnosti funguje úplně jinak, protože příkaz switch je úmyslně navržený takhle:

      |
  ----+-----
 /  switch  \
--+--+--+--+--
  |  |  |  |
  |  |  |  |  +---------+
  |  |  |  +--+ příkaz1 |
  |  |  |     +----+----+
  |  |  |          |
  |  |  |     +----+----+
  |  |  +-----+ příkaz2 |
  |  |        +----+----+
  |  |             |
  |  |        +----+----+
  |  +--------+ příkaz3 |
  |           |  break  +--+
  |           +---------+  |
  |                        |
  |           +---------+  |
  +-----------+ příkaz4 |  |
              +----+----+  |
                   |       |
                   +<------+
                   |

To je důvod proč se u každé větve musí psát ten break, aby se dosáhlo toho prvního stavu, zrovna break je tam jaksi navíc a umožňuje ten blok příkazů zase ve vhodném místě podle potřeby přerušit, klidně i podmíněně. Takže je to jeden blok s několika vstupními body, a pomocí break se z toho dá udělat SELECT.
Pro tyhle dvě možnosti by tedy "správně"(?) měla být dvě různá klíčová slova, jenže tvůrci Céčka si byli vědomi toho že po překladu do strojového kódu tam stejně zbydou jen nějaké CMP a JNZ a tak to všechno vohrábli jedním víceúčelovým příkazem, podobně jako celý zbytek tohohle jazyka, aby si nekomplikovali život a kompilátor. To jen potvrzuje názor, že C je nízkoúrovňový systémový jazyk, postavený jen o málo výš než assembler, který se sice moc nehodí na psaní větších programů, ale je dokonalý pro malé a rychlé kostky unixových skládaček: http://cm.bell-labs.com/cm/cs/who/dmr/unixblocks.jpg Přesto ten jazyk má na víc, je otázka jestli na to mají i jeho programátoři.
Čili, defaultní možnost je, stejně jako u VŠECH ostatních příkazů, že se jede dál dokud je kam, a pokud to potřebuji z jakéhokoliv důvodu v libovolném místě přerušit, tak to udělám úplně stejným příkazem jako ve VŠECH ostatních případech - příkazem break. Kdyby to zrovna a jedině u switche bylo opačně, tak by to bylo pro programátora matoucí, navíc proti principům UNIXu - jednotnosti a univerzálnosti.
Nakonec, právě tebou zmíněný příkaz for je nejlepší ukázka toho jak univerzálně K&R uvažovali, protože do něj se dá zadat kromě běžného počítaného cyklu tolik nej(h)různějších možností a činností, že se k němu nakonec ani žádný blok příkazů psát nemusí (což neznamená že to není hrozná prasárna).

Ještě mě tak napadá, že třeba účelem ternárního operátoru určitě nebylo primárně tohle použití:

#define KOUPELNA 1
#define ZACHOD 2
#define CHODBA 4
unsigned int vypinace;
...
printf("V koupelně %s.\n", vypinace&KOUPELNA ? "se svítí":" je zhasnuto");
printf("Na záchodě je %s.\n", vypinace&ZACHOD ? "rozsvíceno":"zhasnuto");
printf("V chodbě %s.\n", vypinace&CHODBA ? "projdeš bezpečně":"se přerazíš");


anonymous

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #10 kdy: 06. 11. 2010, 14:30:22 »
0xe0-0xe2 na x86. http://faydoc.tripod.com/cpu/loopnz.htm

Proto je imho chyba, pokud návrháři jazyka zvolili tu, která vede k delšímu, méně čitelnému kódu s větší pravděpodobností chyb.

to rozhodne. oproti pythonu je to rozdil, protoze tam je rec o hll, stejne tak haxe, java ...
c prolina konstrukcema do asm stejne jako basic.

je to oldschool, ale tenkrat nic moc lepsiho neznali. pokud vim computed goto v basicu, stejne jako ve fortranu ma taky implicitni fallthru (na konci statementu mas goto ven).

a vubec to je secko desne OT :))

Inkvizitor

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #11 kdy: 06. 11. 2010, 15:01:47 »
Radovane, programovací jazyk C je tak jednoduchý, že by pro nikoho nebyl problém se naučit, že dalším 'case' prostě vyhodnocováním implicitně končí a dokonce si myslím, že by to bylo intuitivnější. Zvlášť u jazyka, který je "jako břitva" bych to neviděl jako sebemenší problém. Podle mého názoru autoři zkrátka nedomysleli důsledky nebo si zjednodušili návrh kompilátoru (museli by pravděpodobně zavést další klíčové slovo, protože třeba 'continue' by mohlo být opravdu matoucí).

Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #12 kdy: 07. 11. 2010, 00:07:50 »
Citace
To že auto může vjet do protisměru také vede k nehodám, a přesto se jízdní pruhy neoddělují betonovou bariérou. Ono se sice v 90% případů jezdí vpravo, ale občas se jiná možnost hodí, třeba při předjíždění nebo objíždění rozkopané silnice.
Proto taky je standardně volant vlevo - tzn. usnadňuje nejčastější použití auta: jízdu vpravo, ale nikterak neznemožňuje jet vlevo. Akorát ty nejlepší auta Ti zavibrují volantem, když uděláš nestandardní krok - přejedeš dělící čáru. Návrh C switch/case mi ale připomíná auto s pravostraným řízením, (plánované do evropy).

Zbytek naprosto nechápu, co tím chceš vyjádřit, já se nepřu o užitečnost této konstrukce - sám ji občas použiju, ale tvrdím, že jelikož to není časté použití, zatímco jednoduchý rozskok ano, tak by měla být explicitně označena možnost pokračovat a ne možnost ukončit.

Inkvizitor:
No mě by tam to continue ani zas tak nevadilo. Zaprvé by to odstranilo disproporci, kdy break se uvnitř switch týká jiného bloku kódu než continue. Zadruhé - break v cyklu znamená: "ukonči tuto iteraci a skoč na konec cyklu". U switch znamená: "ukonči tento blok a skoč na konec switche" - v podstatě se v definici zamění blok za iterace a cyklus za switch. Když tedy "continue" u cyklu znamená: "ukonči tuto iteraci a začni novou iteraci", stejnou substitucí dostávám význam continue u switche: "ukonči tento blok a pokračuj následujícím blokem". Což je přesně to, co by příkaz continue měl umět.
 Pak by jen stačilo zadefinovat, že case znamená ve skutečnosti break; case a jsme doma.
A kdyby např. uměl ještě continue <case>, to by byla lahůdka :-) stejně jako tam chybí možnost breaknout víc cyklů najednou apod....
« Poslední změna: 07. 11. 2010, 00:14:23 od Logik »

Radovan

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #13 kdy: 07. 11. 2010, 09:14:00 »
Auto, které mi zavibruje volantem ve chvíli kdy se vyhýbám slepici (ať s peřím či bez), bych za nejlepší rozhodně neoznačil ;) Kromě toho existují auta s řízením uprostřed, které by bylo z mnoha důvodů nejvýhodnější. Ovšem tradice a tupost zákazníků...

Rozskok je možná častěji používaný, ale já to vidím tak, že patriarchové tenkrát z toho jazyka prostě vyházeli všechno co nebylo nepostradatelné - a dobře udělali - a místo dvou jednoúčelových příkazů tam dali jen jeden, který umí oboje. A kvůli jednoduchosti kompilátoru i jednoznačnosti výkladu kódu to prostě dělá tak jako všechny ostatní cykly: breakem se dá vyskočit z bloku/cyklu ven.

Naproti tomu continue přeskočí na podmínku pro novou iteraci, což by u switche muselo znamenat že se začne zase od začátku - skokem na switch a novým výběrem některé "větve". Continue <case> je blbost, protože je to úplně to samé jako goto label, a to už tam přece je, a právě od toho tam je! (Stejně jako na breaknutí více cyklů najednou.) Tak na co mu dávat další název jen proto, aby se s ním mohlo omezeně skákat jen uvnitř jednoho bloku kódu? Co by na použití goto řekl Ken Thompson a jeho jazyk B, přímý předchůdce Céčka?

loop :
   while((c=char(fmt,i++) ) != '%') {
      if(c == '*e')
         return;
      putchar(c);
   }
   x = *adx++;
   switch c = char(fmt,i++) {

   case 'd': /* decimal */
   case 'o': /* octal */
      if(x < O) {
         x = -x ;
         putchar('-');
      }
      printn(x, c=='o'?8:1O);
      goto loop;

   case 'c' : /* char */
      putchar(x);
      goto loop;

   case 's': /* string */
      while(c=char(x, j++)) != '*e')
         putchar(c);
      goto loop;
   }
   putchar('%') ;
   i--;
   adx--;
   goto loop;
}

Je to sice něco trochu jiného, ale účel goto je z toho úplně jasný. A B je zase odvozené z BCPL, v jehož manuálu čtu:

switchon E into <block>
where the contains labels of the form:
case <constant> : or
default :
...
The switch is implemented as a direct switch, a sequential search or a hash switch
depending on the number and range of the case constants.

Labels se tam píše, labels! A jediný důvod proč tam jsou je ten, že se na ně může skákat. Ta dvojtečka by měla dost naznačit už sama o sobě, ne? Od běžných labelů se liší jen tím case, aby bylo jasné že na ně může skočit jen nejbližší switch. Kdyby to ale mělo fungovat čistě jako rozskok, muselo by tam být něco takového:

switch(n) {
    case 1 {...}
    case 2 {...}
    default {...}
}

Což je teda pěkná hrůza, vůbec to do stylu toho jazyka nesedí. Takže je to tak že mezi { a } je jeden blok - sekvence příkazů, a ten se vykonává od místa kde do něj vstoupíš až do jeho konce, nebo prvního nalezeného break. Tak to funguje naprosto stejně pro switch, for, while i do, a byla by velká chyba to chtít v jednom z těch případů udělat opačně. Navíc jen proto, aby se omezily možnosti toho příkazu.

Prostě je tam jednou switch(n){příkazy}, kde se podle hodnoty toho n dá určitá část příkazů přeskočit, a nemá smysl nat tím ronit slzy, zvlášť že se s tím dá takhle dělat mnohem víc věcí než kdyby to mělo fungovat jinak, čili, jak napsal Jiri, "zapomenutý break ve switchi není chyba", je to prostě základní možnost, která se dá pomocí breaku rozšiřovat o další funkce.

Inkvizitor: no to je právě ono, programátoři by se museli naučit že to právě a jedině u switche funguje odlišně od všech těch cyklů, a také by se to musel naučit kompilátor. A tehdejší kompilátory musely fungovat v pár kilobajtech paměti ;D Místo toho nevhodného continue by se tam dal použít třeba next, ale to by se třeba mě pletlo s basicovým next, které dělá totéž co continue - začíná novou iteraci. Čili nám zbývá jen ty case inteligentně seřadit, nebo použít jednoduché a rychlé goto.

Logik

  • *****
  • 1 022
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #14 kdy: 07. 11. 2010, 13:26:35 »
Citace
Kromě toho existují auta s řízením uprostřed, které by bylo z mnoha důvodů nejvýhodnější. Ovšem tradice a tupost zákazníků...
Nevim, ale tendle argument mi připadá ve stylu, všichni jsou blázni, ale já jsem letadlo... Navíc nepopíráš smysl přirovnání, ale jeho nepodstatnou složku (nejde o to, jestli je to skutečně vhodné nebo ne, ale že se to tak dělá, protože je obecně příjmanej názor, že to je neergonomičtější vzhledem k použití auta).

Citace
Naproti tomu continue přeskočí na podmínku pro novou iteraci...
Jenže ve switchi žádné iterace nejsou. To prostě není cyklus a nejsou tam žádné iterace. Jak jsem v předchozím postu, význam break by šlo krásně krásně konzistentně rozšířit i na ne-cykly.
(myšlenka: switch není cyklus, ale jednotlivé case větve lze brát jako iterace. Pak to sedí daleko lépe, než současný stav, kde continue u switche nemá význam, zatímco break ano).

Citace
Continue <case> je blbost,
break je blbost, continue je blbost. To vše lze nahradit příkazem goto. Jenže všechny tyto konstrukty mají jednu výhodu - jsou o mnoho znaků kratší a zároveň neumožňují skočit někam, kde to bude na 99% špatně. Goto by tak zůstalo opravdu na "hacky" a vyloženě nestandardní konstrukty, což by opět omezilo chybovost.

Citace
"zapomenutý break ve switchi není chyba",
Zaprve zapomenutý break chyba je. Chyba není úmyslně nenapsaný break. Což ukazuje na nevhodnost konstruktu: člověk má ořemýšlet proč něco někde je a ne proč něco někde není. Dobrý jazyk, pokud má daný výraz dva možné významy, tak by měl předpokládat standadní užití a vyžadovat označení nestandardního. jinak to prostě vede k těžko odhalitelným chybám.

Zadruhé diskutuješ z pozice je to takto, takže je to dobře. Čili je to podobný argument, jako kdybych tvrdil - v atari basicu bylo pouze goto, takže je dobře, že je tam pouze goto. Proč si tedy každej, kdo moh, instaloval turbo basic, kde byly cykly?

Citace
Takže je to tak že mezi { a } je jeden blok....
A kdo mi brání se na case dívat jako na oddělovač bloků? Jednořádkovej for cyklus má taky na konci implicitní "continue", i když tam není složená závorka. Zkus trochu vystoupit ze svýho vidění "je to prostě takhle" a připustit i jiné možnosti. Já nad tím neromím slzy, já prsotě konstatuji, že kdyby byl návrh jazyka jiný, tak by docházelo k míň chybám ve zdrojácích. Např. by se nikdo nesháněl po softwaru, kterej bude auditovat všechny case větve neukončené break.

U zbytku jsem moc nepochopil vztah (co s tim má společnej jazyk B: opět argumentuješ tím jak to je, zatímco jáse bavím o tom, jak by to bylo dobře - to se jako nesmí následník jazyka vylepšit?), popř. argument, že by to zesložitilo kompilátor (naprosto marginálně).