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

Ondy

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #15 kdy: 07. 11. 2010, 14:01:52 »
Existuje spousta toolu pro statickou analyzu kodu. Z komercnich je hojne pouzivany Fortify 360, z open-source jsem pouzival SPlint (u nej ale nevim jak je to s vyvojem, mam pocit ze aktivita vyvojaru je miziva), hodne zajimavy se mi zda Frama-C. Preji hezky den.


Radovan

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #16 kdy: 07. 11. 2010, 15:45:16 »
... protože je obecně příjmanej názor ...
Což nemusí nutně znamenat že je to pravda 8) Tohle je argument typu "to ví přece každý" nebo "říkali to v televizi".
Citace
Jenže ve switchi žádné iterace nejsou.
Ne, nejsou. Break znamená "vypadni ven" z příkazu, continue říká "sjedeme to znovu". Proč nefunguje break i u jednotlivých větví if? To by pak podle tvého vzoru toho switche musel přeskočit na opačnou větev, nemyslíš?
Citace
... jsou o mnoho znaků kratší a zároveň neumožňují skočit někam, kde to bude na 99% špatně.
Goto l1 není delší než continue :P Ne, vážně, jsou tam proto že se vztahují jen k těm čtyřem konkrétním příkazům a nedá se s nimi skákat libovolně, takže to goto tam zůstává jen na skutečné hacky jako přeskok na jiné case, případně vyskočení z více cyklů najednou, což je podle mě také dost minoritní potřeba. Osobně jsem ho v C ještě nepoužil!
Citace
Zaprve zapomenutý break chyba je. Chyba není úmyslně nenapsaný break.
Jenže pro kompilátor to je jiný tvar toho příkazu, neznamená to z jeho pohledu o nic víc než když na požadovaném konci řádku zapomenu napsat \n.
Citace
Proč si tedy každej, kdo moh, instaloval turbo basic, kde byly cykly?
Asi jim ten Atari Basic z nějakých důvodů nevyhovoval, mě se v Sinclair Basicu naopak goto líbilo, obzvlášť lahůdková schopnost provést GOTO x*1000 mi po přechodu na "dokonalejší" PC dost citelně chyběla. (ON GOTO to neznalo, takže jsem si vhodným očíslováním řádků nahradil celý ten switch, aniž bych předtím nějaké C nebo něco podobného kdy viděl, nebo tušil něco o strukturovaném programování.) Ale fakt je že i se samotným goto se vystačit dá, vlastně by v úplně minimálním jazyce stačily jen dvě věci: přiřazení a podmíněný skok. Nic víc není potřeba.
Citace
A kdo mi brání se na case dívat jako na oddělovač bloků?
Ne kdo, ale co, ta dvojtečka přeci. Ta ti říká že je to label a nic víc.
Citace
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ě).
B s tím má společného to, že je to předchůdce C, a jeho předchůdce je zase BCPL, jeho předchůdce CPL, jeho předchůdce... Tak do hloubky to studovat nehodlám, musí ti stačit že v BCPL byl příkaz switchon, který se choval stejně jako switch v C, kde se to vylouplo poprvé netuším. Ale řekl bych že takhle se dostaneme až k assembleru a strojovému kódu, které všichni ti co tyhle jazyky pro svojí potřebu vymýšleli znali a zvládali levou zadní. Prostě to vychází ze samotného principu fungování počítačů a je to tak dodnes.
A s tím vylepšováním to nebude tak žhavé, už v době tvoření toho C to neudělali, a třeba v Javě switch funguje také úplně stejně, i když je to jazyk o pár desetiletí mladší a údajně z něj byly odstraněny konstrukce, které dělaly programátorům v C problémy. Nakonec, když je to tak marginální zesložitění, tak si kompilátor uprav, vyzkoušej si co ti to udělá a uvidíš sám. Nebo si pro sebe vytvoř úplně nový jazyk který ti bude vyhovovat, tyhle vykopávky se už stejně používají příliš dlouho a chtělo by to změnu, něco minimálně tak odlišného jako byl Prolog ::)

Prostě se hádáme zbytečně, máme dva opačné náhledy na jednu věc, já si myslím že ten můj je bližší tvůrcům těch jazyků. Radši prozraď, ať z nás vypadne konečně něco užitečného, co ty používáš na vychytávání svých chyb typu zapomenutého breaku? Pro mě osobně je nejlepší živý člověk, kterému dám svůj program přečíst a sleduji jak hodně se šklebí. To zatím žádný program nahradit nedokáže.

Logik

  • *****
  • 1 027
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #17 kdy: 07. 11. 2010, 20:36:17 »
Citace
Tohle je argument typu "to ví přece každý" nebo "říkali to v televizi".
Nepochopil. V tom přirovnání vůbec nejde o tom, jestli to je nebo není dobře. V tom přirovnání jde o to, že se ví, že se jezdí vpravo a tak se tomu to auto přizpůsobí.
Ale u switch konstruktu se ví (statistika), že nejpoužívanější je rozskok se separátníma blokama (za každym break), ale ten konstrukt k tomu přizpůsoben není.

Pokud konstruktéři auta předpokládají, že nejlépe se řídí s volantem vlevo a není tomu tak, je to jejich chyba, ústupek přání zákazníka či obecným zvyklostem (by se to nepletlo). Pokud návrháři jazyka navrhnou switch tak, jak je v C, tak se nepoužívá dobře. To lze objektivně změřit (délka nejčastěji používaných kódů, frekvence chyb). Např. u JAVY byl návrh ústupkem obecným zvyklostem (píše se to takhle). U C (respektive B) nebyly ale ani obecné zvyklosti ani přání zákazníka, zbývá tedy chyba...

Citace
Break znamená "vypadni ven" z příkazu.... Proč nefunguje break i u jednotlivých větví if?
Právě proto, že break NEZNAMENÁ vypadni ven z příkazu, ale z cyklu. A právě proto by šlo krásně sémanticky rozšířit i na switch.

Citace
Goto l1 není delší než continue :P
l1:
...
Goto l1
delší je. A jestli chceš svůj názor obhajovat prasečinama jako dvoupísmenej název identifikátoru...

Citace
Jenže pro kompilátor to je jiný tvar toho příkazu...
Tvrdil jsem snad, že je to syntaktická chyba? Ne.
Dělá to to, co bylo zamýšleno? Ne? Tedy je to chyba.
To, že to není syntaktická ale sémantická chyba je o to hroší -
člověk na ní něpřijde při překladu, ale později.

Citace
Ne kdo, ale co, ta dvojtečka přeci. Ta ti říká že je to label a nic víc.
Takže výraz (1+1) je volání funkce? Jsou tam závorkly a ty přeci znamenají volání funkce.

Citace
Jazyk B...
Znova. To, že to je v B nebo v assembleru tak neznamená, že to v těch jazycích bylo dobře. Takže to není argument. Vždycky se jezdilo na koních a taky to není nejlepší způsob překonávání vzdáleností.
 Ad Java - tendle jazyk dědí C++ syntaxi. Pravda odstraňuje některé nebezpečné věci, ale to je rozdíl oproti tomu "přehodit" význam. V době vývoje C to problém nebyl, protože B nemělo v podstatě uživatelskou základnu a jazyk C jazyk B nahrazoval. Teď už to samozřejmě člověk těžko změní, jen by se mu to pletlo. Ale to neznamená, že nemůže konstatovat, že to je špatně.

Btw. ve všech jazycích, co znám a co nemají C-like syntaxi se case používá bez pokračování dalšími větvemi.

Citace
případně vyskočení z více cyklů najednou, což je podle mě také dost minoritní potřeba. Osobně jsem ho v C ještě nepoužil!
A koliks toho v C napsal? Např. právě u switch příkazu uvnitř cyklu je to vcelku častá potřeba jako jedna z možností ukončit cyklus...

Citace
Prostě se hádáme zbytečně, máme dva opačné náhledy na jednu věc, já si myslím že ten můj je bližší tvůrcům těch jazyků....
Já Ti Tvůj náhled neberu. Já jen tvrdím, že z objektivních hledisek (jako je např. délka kódu u často používaných konstrukcí, odolnost vůči programátorským chybám) není návrh konstrukce switch v jazyce C dobrý. S mým, tvým či K&R pohledem na věc nemá mé tvrzení žádnou souvislost, jak délka kódu, tak frekvence chyb daného typu je veličina objektivně měřitelná :-)
« Poslední změna: 07. 11. 2010, 22:38:29 od Logik »

Radovan

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #18 kdy: 08. 11. 2010, 20:45:17 »
V tom přirovnání jde o to, že se ví, že se jezdí vpravo a tak se tomu to auto přizpůsobí.
A víš také odkdy se jezdí vpravo? Zase tak dlouho to není, sotva dvě staletí. Prozkoumej pár starých kočárů a povozů, a spočítej si kolik z nich má brzdu (tu kliku) na pravé a kolik na levé straně. A nebo lépe, proč je spojka v autě vlevo i u aut s levostranným řízením, když to u spousty z nich způsobovalo konstrukční problémy? Pokud chceš pochopit proč to tak je, budeš muset prostudovat konstrukci a používání dopravních prostředků za poslední tři tisíce let, s obzvláštním důrazem na období 1890 až 1920.
Pak se vykašleš na nějakou statistiku (která navíc za pár let nemusí platit) a dojde ti, že v C je switch udělaný tak jak je právě proto, že takhle to funguje v assembleru, ne kvůli nějaké četnosti použití, ale proto že před padesáti lety si ti lidé co ho používali potřebovali usnadnit práci a udělali to podle toho co znali a dělali každý den. Vyšší jazyky vycházející z jiných principů potom používají i jiný způsob větvení. Když se ti ten nízkoúrovňový v C tolik nelíbí, proč nepoužíváš třeba C#? Tam je to takhle:

case 2:
    Console.WriteLine("n is an even number.");
    goto case 3;
case 3:

Nedědí snad C# céčkovou syntaxi stejně jako Java? Přesto je to v něm opačně, jako v Pascalu. Ale abych se přiznal, pouhý pohled na tenhle kód mi způsobuje nepříjemné pocity v oblasti žaludku >:( Možná že mix BASICu, Pascalu a C++ na závěr přišlehaný Javou je na mě opravdu moc, asi jako když pejsek s kočičkou pekli dort. A ještě navíc NUTÍ programátora použít goto v místě, kde v C není potřeba napsat nic, to je fakt vylepšení! Ten původní céčkový způsob mi prostě připadá mnohem přirozenější (možná právě proto že je bližší strojům), a to jsem v assembleru nikdy nic pořádného nenaprogramoval. Pokud tedy nepočítám C jako maskovaný assembler, což skutečně je.
A ještě navrch se zeptám, existuje pro C# nějaký nástroj, který odhalí ve switchi zapomenuté goto? To je přece úplně stejná "chyba" jako v C zapomenutý break! To se prostě nedá poznat, protože by musel upozornit na všechny větve které by podle něj mohly být opomenuté, a těch by tam mohl být také pěkných pár tisíc. Prostě jako když při if(a=b) dostanu warning, zatímco při if((a=b)) je všechno v pořádku.
Citace
Právě proto, že break NEZNAMENÁ vypadni ven z příkazu, ale z cyklu.
while() příkaz; nebo while() {složený příkaz}
for() příkaz; nebo for() {složený příkaz}
switch() {složený příkaz}
Tenhle "příkaz" jsem měl na mysli.
Citace
A jestli chceš svůj názor obhajovat prasečinama jako dvoupísmenej název identifikátoru...
On to l1 byl vtip, ale kolik těch labelů chceš v jedné funkci mít? Jeden, dva... I to je víc než dost, a stojí za to se zamyslet jestli to bez nich opravdu nešlo... Navíc, když tam budu mít jeden label a pět goto tak jsem stejně pořád v plusu.
Jo, asi jsem toho opravdu nenaprogramoval dost abych se bez goto neobešel, to je pravda, párkrát jsem si radši vypomohl nějakým flagem, což ale zase vede ke složitější podmínce v několika vnořených cyklech, netroufnu si rozhodnout jestli je to lepší nebo přehlednější než kdybych tam prostě plácnul goto. Každopádně je to ve výsledku delší a pomalejší, ale není tam to strašně škodlivé goto ;)
Něco jiného by bylo, kdyby šlo použít třeba break 3 pro přerušení tří cyklů najednou, případně continue 3 s podobným významem. To bych si dovedl představit jak používat, i když si myslím že by to zase bylo méně přehledné než prosté goto label.

P.S. Pod většinu toho co jsem tu od tebe četl bych se klidně podepsal, a nerad bych se s tebou o tuhle jednu věc nekonečně hádal jako s Laelem, se kterým jsem se naopak neshodl téměř na ničem, takže prostě končím tím jak to chápu a řeším já, abych tam ten break nezapomněl napsat:

1. Každá větev switche je ukončená breakem, stejně jako každá funkce je ukončená returnem.
2. Pokud nechci aby větev skončila nebo funkce vracela hodnotu, tak tam break nebo return nepíšu.

Toť vše, prostě to ve všech případech beru stejně, podle té unixové jednotnosti, tak jako tebou jinde jmenovaný design "vše je soubor".

Logik

  • *****
  • 1 027
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #19 kdy: 09. 11. 2010, 01:49:24 »
Ad historie. V asembleru je to natolik odlišné, že by to problémy nezpůsobovalo. Dokladem toho je, že co vím žádný jazyk se syntaxí neodvozenou od C (ať už jde o pascal, python, ruby, plsql, visual basic, fox pro, eifell, perl, bash, C#) nemá příkaz switch s automatickým propadem do další větve. IMHO už to zcela jasně ukazuje, že ta konstrukce v C nebyla volena štastně, když to všichni ostatní mají jinak.

Citace
Prostě jako když při if(a=b) dostanu warning, zatímco při if((a=b)) je všechno v pořádku.
Ale vždyť to je přesně ono. Normální je nedělal v ifu přiřazení a jazyk by měl být navržen tak, aby varoval, když děláš něco nestandardního. Ty dvojité závorky tu slouží jako upozornění: ano, vím, že dělám něco nestandardního, něco, co na daném místě není běžné a co může být typo chyba či opomenutí. A proto to explicitně označuji. Pomíjím tedy možnost, že člověk udělá dvojité závorky jen tak, to je u mě čuně a toho nezachrání nic :-)

 Stejně tak se v majoritním případě používá case jako rozskok, pokračování do dalšího case je minoritní, a často se dělá chyba v opomenutí break. Proto by měl být jazyk, stejně jako u = a == v ifu, navržen tak, aby standardní zápis vyhodil alespoň varování a umožnil ho explicitně označit za správný (např. formulkou continue, nebo jako v C# goto case x, i když to se mi také moc nelíbí).


Ad C#: Právě proto, že zapomenuté breaky způsobovaly chyby, není v C# legální na konec neuvést nic, vždy tam musí být goto nebo break.  Asi jsi se tedy chtěl zeptat, jak se to dělá v perlu, kde lze explicitně přeskočit na další pomocí continue a break je implicitní. No právě pro lidi je přirozené, že větev dalším case končí, takže jim nedělá problém označit explicitně přeskok na jinou větev. Stejně jako by dělali chyby, pokud nějaký jazyk by zaved prioritu sčítání před násobením, tak dělají chyby, když C zavedlo prioritu pokračování před ukončením. Prostě jedna možnost přirozená je, druhá nikoli. 

Co se týče C#, ideální to imho také není, je to ale výsledek toho, že se návrháři C# (asi správně) neodvážili u C like jazyka změnit smysl příkazu. Neboť to by způsobilo přesně to, v čem je problém switche v C: člověk automaticky napíše něco co čeká, že se chová tak a ono by se to chovalo jinak.
Zas si ale byli vědomy velké problematičnosti toho konstruktu a tedy přidali bezpečností obstrukci. To, že v podstatě explicitně oproti Javě a C nutí člověka psát "zbytečnosti" jen více jen dokládá, že to problém je. Takže C# řešení je z nouze ctnost.


Citace
Tenhle "příkaz" jsem měl na mysli.
 
Argumentuješ kruhem. Pokud se bavíme o správné sémantice "break" v konstruktu switch, nemůžeš argumentovat sémantikou break v konstruktu switch. A pokud vezmeš všechny ostatní případy, kdy je break použito, vždy jde o cyklus. Naopak ve všech ostatních "necyklových" konstrukcích (if, funkce, blok) příkaz break použít nelze.
  Z toho plyne, že break v příkazu switch má nestandardní a posunutou sémantiku (to dokládá i to, že jedině ve switch nelze užít continue na místě break.) Proto nevidím žádný problém, kdyby ta sémantika byla posunuta trochu jinak.
  Ono právě to, že nejde jednoduše říct - např. break ukončuje cyklus, ale musíš dát seznam příkazů, které ukončuje, ukazuje na to, že není definováno "konzistentně" (rozuměj u všech příkazů stejně - viz if).
 
Citace
1. Každá větev switche je ukončená breakem, stejně jako každá funkce je ukončená returnem.
2. Pokud nechci aby větev skončila nebo funkce vracela hodnotu, tak tam break nebo return nepíšu.
Rozdíl je ten, že případ 2, pokud nastane omylem, pro funkci kompilátor odchytí (function should return value), ale pro switch nikoli. Pokud se opravdu umíš pohlídat, že nikdy nezapomenš break, tak Tě to ctí. Z praxe se ale ukazuje, že ostatní s tím problémy mají :-)... jak ukazuje i úvodní dotaz v threadu (admini, nejde tu polemiku přesunout do samostatného threadu? :-)).
« Poslední změna: 09. 11. 2010, 01:57:41 od Logik »


Radovan

Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #20 kdy: 09. 11. 2010, 04:38:28 »
Ad historie. V asembleru je to natolik odlišné...
C:

switch()
{
case 1: příkaz 1
        příkaz 2
case 2: příkaz 3
        příkaz 4
        break
case 3: příkaz 5
        příkaz 6
}
příkaz 7

ASM:

jumptable...
label1: instrukce 1
        instrukce 2
label2: instrukce 3
        instrukce 4
        jmp dolu
label3: instrukce 5
        instrukce 6
dolu:   instrukce 7

Neřekl bych že se to nějak výrazně liší ;-) Koukl jsem se na ten Perl (zkráceně z Wikipedie):

use feature 'switch';
given ($foo) {
    when (undef) {...;}
    when ("foo") {...;}
    when ([1,3,5,7,9]) {...; continue; # Fall through}
    when ($_ < 100) {...;}
    when (\&complicated_check) {...;}
    default {...;}
}

Jo, takhle to beru, však jsem to s těmi závorkami navrhoval. Perl neznám, takže mě těší že nejsem první koho to napadlo, aspoň vidím že to nebyla blbost. Akorát u toho continue mi to úplně nesedí, takhle mi není jasné jestli se při něm vyhodnotí ($_ < 100) jak bych očekával, nebo se automaticky přeskočí rovnou do další větve. Předpokládám že se ta podmínka vyhodnotí, protože v Perlu to funguje jako pomalá série ifů a nezávislých bloků, zatímco v C je to rychlá jumptable do jednoho bloku.
Citace
Normální je nedělal v ifu přiřazení...
Jenže v C to normální je (přiřazení je výraz), a s tím spousta dalších podivných věcí. Pro mě ty vnitřní závorky neznamenají nic nestandardního, ale jasně říkají překladači že je mezi nimi výraz, kterého výsledek má vyhodnotit. No, stejně je tam obvykle dopíšu až po tom warningu :P Jenže ten také dostanu jen když ho chci a zadám -Wall, jinak je to kompilátoru úplně fuk. (Osobně jedině -Wall -pedantic, nikdy jinak.)
Citace
Takže C# řešení je z nouze ctnost.
Spíš splácanina páté přes deváté, tedy maloměkký standard. Navíc to nejsou jen obstrukce ale navrch spousta omezení, co všechno se tam nesmí použít, prostě ti hodně bere ale nedává nic výměnou. Psát break i v poslední větvi se doporučuje v každé učebnici C pro začátečníky, asi proto aby si vtloukli do hlavy že v tomhle jazyce se prostě psát musí.
Osobně bych spíš uvítal, kdyby překladač ohlídal neinicializované proměnné, protože u tohohle žádnou hlášku nedostanu:

int a,b;

if((a=b))
    printf("OK\n");

Přitom je to očividná díra, nebylo by lepší kdyby se KAŽDÉ proměnné už při deklaraci MUSELA přiřadit nějaká hodnota? Možná že by se tím zabránilo víc chybám a vráskám než implicitním breakem, každopádně jsem s tím už párkrát narazil, zatímco break jsem fakt nezapomněl. Napsal jsem ho i tam kde jsem ho nechtěl ;D
Mimochodem, třeba BASIC, až na ty nejprimitivnější verze, implicitně přiřadí každé nové proměnné nulu, což také není dobrá vlastnost. Jak se říká, nikdy nespoléhej na to co by měl udělat někdo jiný, jako s tím breakem.
Citace
Argumentuješ kruhem.
 
Nikoliv, v kruhu se motáme. Viz ten Perl.
Takže výsledek je stále tentýž, C a pár dalších prehistorických jazyků má implicitní fall-through, modernější jazyky ho nemají. Tenkrát se to tak dělalo (a každý očekával že to tak bude fungovat), pokud musíš za každou cenu pořád jezdit Pragovkou z roku 1937, nediv se že sedíš u chodníku a řadíš levou rukou. Spíš buď ještě rád že nemusíš řadit pákou zvenku auta, jako o dalších dvacet let dříve.

Logik

  • *****
  • 1 027
    • Zobrazit profil
    • E-mail
Re: Audit zdrojového kódu v C (prípadne v C++)
« Odpověď #21 kdy: 09. 11. 2010, 12:34:49 »
Citace
Jenže v C to normální je (přiřazení je výraz)\
Na daném místě to je z 95% chyba. Proto má před tou chybou jazyk chránit. Je úplně jedno, že je to normální C výraz. Je to stejné, jako některé konstruktory v C++ se označují jako explicit, aby se odchytila náhodná nechtěná konverze. Taky by to šlo i bez toho, ale pokud ta konverze běžně nemá smysl, je chyba ten konstruktor tak neoznačit.

Jinak co vím, tak snad kompilátory varují před použitím undefined proměnných, aspoň ty, co jsem používal já... ale je fakt, že u GCC teď nevim....

Citace
Takže výsledek je stále tentýž, C a pár dalších prehistorických jazyků má implicitní fall-through, modernější jazyky ho nemají.
No ale to v podstatě znamená, že návrh C nebyl dobrý a proto ho v novějších jazycích změnili, ne? Kdyby to bylo dobře, tak by to v modernějších (no, modernějších, takový pascal je z stejné doby jako C a nemá to :-)) jazycích zůstalo stejně, ne?

PS: jinak v C a Assembleru vidím dosti podstatný rozdíl: v asm je to jumptable (čili seznam návěstí), v C je to switch (volba). Takže když si to přečte laik nezatížený programováním, tak u jumpu si řekne - program skočí tam a pokračuje. U volby si řekne - program zvolí jednu z možností. V C je to prostě neintuitivní - a to vede k chybám.
Jumptable taky funguje jinak, jde prostě o indexovaný skok, v C specifikuješ hodnoty.
Takovej pascal - vzniknul plus mínus stejně - to má ještě o něco podobnější asm a má to bez problémů jinak.