Portál AbcLinuxu, 30. dubna 2025 16:48
typedef struct Array Array; extern Array *Array_new(int size); extern void Array_push(Array *p, void *v); extern void *Array_get(Array *p, int i); extern int Array_len(Array *p); extern void Array_free(Array **p);array.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "array.h" struct Array { int size; int cap; int len; char *array; }; Array *Array_new(int size) { Array *p; p = malloc(sizeof(Array)); p->size = size; p->cap = 0; p->len = 0; p->array = NULL; return p; } void Array_push(Array *p, void *v) { char *newarray; if (p->array == NULL) { p->array = malloc(p->size); p->cap = 1; } else if (p->len >= p->cap) { p->cap *= 2; newarray = realloc(p->array, p->cap * p->size); p->array = newarray; } memcpy(p->array + p->size * p->len, v, p->size); p->len++; } void *Array_get(Array *p, int i) { return p->array + p->size * i; } int Array_len(Array *p) { return p->len; } void Array_free(Array **p) { free((*p)->array); free(*p); *p = NULL; }Aby položky struktury byly privátní, vidí klient pouze neúplný typ. *A_ints je opaque pointer:
#include <stdio.h> #include "array.h" int main() { Array *A_ints; A_ints = Array_new(sizeof(int)); A_ints->len = 100; /*Preklad selhal.*/Použití:
#include <stdio.h> #include "array.h" void fill_arrays(Array *a1, Array *a2); void print_arrays(Array *a1, Array *a2); int main() { Array *A_ints, *A_floats; A_ints = Array_new(sizeof(int)); A_floats = Array_new(sizeof(float)); fill_arrays(A_ints, A_floats); print_arrays(A_ints, A_floats); Array_free(&A_ints); Array_free(&A_floats); return 0; } void fill_arrays(Array *a1, Array *a2) { int i; float f = 1.1; for(i = 0; i < 15; i++) Array_push(a1, &i); for(i = 0; i < 10; i++) { Array_push(a2, &f); f += f; } } void print_arrays(Array *a1, Array *a2) { int i; for(i = 0; i < Array_len(a1); i++) printf("%d, ", *(int*)Array_get(a1, i)); printf("\n\n"); for(i = 0; i < Array_len(a2); i++) printf("%.1f, ", *(float*)Array_get(a2, i)); } 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 1.1, 2.2, 4.4, 8.8, 17.6, 35.2, 70.4, 140.8, 281.6, 563.2,Ošetřit návratové hodnoty u malloc. Array_get assert neplatný index. Velikosti typu size_t.
Tiskni
Sdílej:
miesto p->cap *= 2; pouzi (mozes) p->cap << 1K tomu dvě poznámky: Jednak by to mělo
p->cap <<= 1
a jednak bych to vůbec nedělal, nemá to IMHO moc smysl. Kompilátor to případně dokáže sám zoptimalizovat, pokud to má smysl.
jednak bych to vůbec nedělal, nemá to IMHO moc smysl.Urcite bych to nedelal a je to velke zlo. Jednak to zastira skutecny zamer programatora, tj. ze chtel nasobit dvema, takze ten, kdo ten kod bude cist po nem, bude muset lustit, o co vlastne slo. A jednak mikrooptimalizace tohoto typu by mel delat vzdy prekladac, protoze ten vi nejlip, jak v danem kontextu a na danem procesoru nejlip/nejrychleji nasobit dvema (jen na x86 jsou k tomu tri instrukce ADD, SHL, LEA, pripadne MUL ;-]). Dalsi vec je, ze s tim clovek muze napachat vic skody nez uzitku. Napriklad znam cloveka, ktery i v C pouziva (X XOR X) k nastaveni hodnoty na 0. Nejspis protoze v davnych casech to byla rychlejsi (a myslim i kratsi) instrukce. Problem je, ze nektere novejsi procesory ten idiom nerozpoznaly, videly tam zavislost v kodu, coz jim zkomplikovalo register renaming a program nakonec jel pomaleji nez mohl.
typedef struct Array Array;Mas 2 krat za sebou Array. To nie je az tak citatelne prehladne. Prave pre ten riadok ale aj vseobecne, za zvykne definovat datovy typ definovat so sufixom _t. A vtedy je jasne, ze v kode je to premenna/struktura a kedy len definovany typ. U strukt niekde najdes aj prefix st_.
Nevím, zda se ten suffix _t hodí zrovna k uživatelským rozhraním.Tak to ries nasledovne:
typedef struct Array_t Array;To je vec kodu. V GTK mas GtkWidget. Lenze tym sa furt pracuje, resp. to je take identicky pre GTK. Napr. velkostne datove typy pre multiplatformove (vid) ma tiez. FILE zas rozlisuje velkym, cize v kode to je prehladne. Hm, prefix st_ sa nepouziva pre struktury. To sa pouziva v strukture stat. Aj ked nic ti nebrani pouzivat ten prefix inde.
#ifndef NAZOV_SUBORU_H #define NAZOV_SUBORU_H /* obsah suboru */ #endifV Array_new() a Array_push() neoverujes malloc(). Pred a po pouzity nuluj pamet. Cez bzero() alebe memset(). Kvoli bezpecnosti - citanie ineho obsahu programu. Ty mozes pred niekym (co ako ked vytvaras a la kniznicu, tak otvoras priestor tym, kt. to budu pouzivat), alebo niekto moze po tebe. Preco ked prepisem cisla 15 a 10, vystup programov je vzdy rovnaky? Pokial sa pocet prvkov nemeni, tak nepouzivaj vo for funkciu a la count(). Ale tu zavolaj pred a hodnotu si uloz do pamete. A nasledne vo for citaj z premennej. Inac musi pri kazdom cykle volat funkciu a la count(), ked vzdy vrati rovnaku hodnotu. Pikoska:
p->cap = 0; p->len = 0;sa da zapisat (mozes sa s tym stretnut):
p->cap = p->len = 0;
Vynulovat paměť před uvolněním memsetem - to je dobrá připomínka, to jsem nevěděl, dík.Ne, neni to dobra pripominka. Drz se zasady, ze kazda funkce by mela delat jednu vec a delat ji poradne. V tomto pripade dela dve veci -- jednak uvolni pamet, jednak ji vynuluje (na kazdy potrebuje pamet mit vynulovanou). Pricemz to nulovani pameti je uplne zbytecne. Jsou to zbytecne provedene operace a hlavne zasvinena cache. (Nulovanim se ti do cache dostanou data, se kterymi uz nebudes pracovat a odstranis ta pouzivana.) Ke vsemu to z pohledu bezpecnosti vubec nic neresi, aby ses dostal k uvolnene pameti, musis bud pouzit jiz jednou uvolnenou pamet (coz je chyba), musis pouzit neinicializovanou pamet (coz je chyba), pristoupit k pameti, ke ktere bys nemel mit pristup (napr. prekrocit hranici pole, coz je opet chyba). Takze v kazdem pripade mas v programu vaznejsi chybu, kterou bys mel resit a kterou vynulovani urcite casti pameti vubec nevyresi.
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.A to Knuth programoval v casoch, ked kompilatory zdaleka nedokazali optimalizovat tak dobre ako dnes. Pri recyklovani pamate v ramci jedneho processu nulovat pamet pri free() je prasarna pre cache (to si tu uz vysvetlili). Pri malloc() maximalne v debug verzii pre deterministicke chovanie pri chybach prace s pamatou. Pri free() sa vtedy dava viac specificka hodnota, napr. 0xDEADBEEF. A keby som chcel byt privacy-paranoik medzi procesmi tak si myslim, ze tych par supercitlivych bytov (kluce napr.) si moze uzivatel vynulovat explicitne.
void *Array_get()
je špatná, respektive je špatné následné přetypování a dereference pointeru na konkrétní typ (kde slovem "špatný" mám na mysli undefined behaviour).
Správně bys měl buď zajistit korektní zarovnání toho interního bufferu, nebo případně použít memcpy()
na čtení prvku místo přímého přetypování pointeru, ale to je docela nešikovné.
V tom případě s těmi integery to asi reálně nějaký závažný problém typu SIGBUS nevyvolá. Na nějaké jiné platformě nebo při použití složitější struktury nebo za nějakých jiných podmínek by to padat IMHO mohlo. Případně to může vést ke snížení výkonu.
sizeof()
v tomhle nijak nepomůže, ale koukám teď, že malloc()
to řeší tak, že prostě alokuje s maximálním zarovnáním pro všechny fundamentální typy (max_align_t
), takže pokud člověk nemá specielní požadavky, malloc()
stačí.
typedef struct large { struct point { int x[100]; int y[100]; int z; } s; float f; long l; } Large;Dle sizeof velikost 816 bytů. Alokoval jsem pole o milionu prvků a zjevně to funguje správně.
malloc()
, nebo zajistil větší zarovnání?
Funkce malloc je schopna detekovat podle parametru (který jde obvykle ze sizeof), že výsledek může vyžadovat větší alignment. Takže pro size < 16 bytes vrátí zarovnání klidně na 8 bytes, ale pro size >= 16 zarovná na 16 bytes, bo paměť může být potenciálně využita na SSE move-aligned instrukci.Zadna takova detekce se nedeje. Pokud si nestanovis jinak, standardni malloc (ptmalloc) zarovnava na dvojnasobek slova (tj. na 64 bitech na 16 B) a minimalni mnozstvi alokovane pameti (bez rezie) je opet dvojnasobek slova (to je dane tim, jak se pracuje s uvolnenymi bloky).
Funkce malloc je schopna detekovat podle parametru (který jde obvykle ze sizeof), že výsledek může vyžadovat větší alignment. Takže pro size < 16 bytes vrátí zarovnání klidně na 8 bytes, ale pro size >= 16 zarovná na 16 bytesAha, jo tak, díky za vysvětlení.
Ještě k původnímu - role sizeof() je samozřejmě důležitá i v tom, že i další elementy v poli budou správně zarovnányJasně, rozumim, co chceš říct, akorát jsem spíš jen rejpal v tom smyslu, že sizeof sám o sobě jen vrátí velikost, to správné zarovnání ve strukturách je dané paddingem (příp. správným řazením prvků).
sizeof() je součást, předává správnou velikost struktury, v důsledku je třeba použít adekvátní alignment.Pre tych ktory nehladali, mozne riesenie ako to pravit: https://en.wikipedia.org/wiki/Data_structure_alignment
packet.u16Flag = cpu_to_le16(packet.u16Flag);To mě přijde strašně náchylné na zapomenutí té konverze.
Tak si na vsetky druhy kopirovania napis wrappery, pouzivaj striktne len tie a optimalizator si uz poradi.Případně by asi šlo to nějak generovat.
Nechci vyvolávat flame, ale přijde mi lepší psát hvězdičku k typu než k názvu proměnné. Chápu, že i u toho názvu dává svým způsobem smysl, ale myslím si, že víc patří k typu (proměnná je typu ukazatel).
Někdo to pak řeší tak, že píše hvězdičku doprostřed (mezery na obou stranách). Schválně můžeš přidat anketu :-)
char *p1, *p2;
Tak to je neco jineho (aspon jak si pamatuji C) nez:
char* p1, p2;
Vím, tohle je pěkná záludnost, ale vzhledem k tomu, že to většinou rozepíšu tak, aby na každém řádku byla jen jedna proměnná, tak mě to až tolik netrápí.
BTW: teď jsem k tomu našel Is ``int* p;'' right or is ``int *p;'' right?:
The choice between ``int* p;'' and ``int *p;'' is not about right and wrong, but about style and emphasis. C emphasized expressions; declarations were often considered little more than a necessary evil. C++, on the other hand, has a heavy emphasis on types.
A ``typical C programmer'' writes ``int *p;'' and explains it ``*p is what is the int'' emphasizing syntax, and may point to the C (and C++) declaration grammar to argue for the correctness of the style. Indeed, the * binds to the name p in the grammar.
A ``typical C++ programmer'' writes ``int* p;'' and explains it ``p is a pointer to an int'' emphasizing type. Indeed the type of p is int*. I clearly prefer that emphasis and see it as important for using the more advanced parts of C++ well.
Podle toho bych měl být typický C++ programátor… a přitom jsem Javista
The choice between ``int* p;'' and ``int *p;'' is not about right and wrong, but about style and emphasis.S tím úplně nesouhlasim. Tahle argumentace:
A ``typical C++ programmer'' writes ``int* p;'' and explains it ``p is a pointer to an int'' emphasizing type. Indeed the type of p is int*. I clearly prefer that emphasis and see it as important for using the more advanced parts of C++ well.... IMHO nedává moc smysl, protože pak máš typ jako třeba
char *foo[]
, kde ty závorky stejně nemůžeš nacpat na levou stranu. V C/C++ to není tak, že typ je nalevno a jméno napravo, nýbrž platí spirálové pravidlo (což je syntaktická šílenost, ale tak to prostě je, for better or worse).
Osobně mně ta syntaxe char *p
dává větší smysl. Jediné místo, kde dávám *
nebo &
k typu je návratová hodnota funkce.
struct Link* first; for (char& ch : line) { Polygon* s1[10]; void f(Shape* q, vector<Circle*>& s0) Shape* p1 = new Rectangle{Point{0,0},10}; int* find(int* first, int* last, int v)Tohle je styl Bjarna Stroustrupa, tvůrce jazyka C++. Takže kdybych něco psal v C++, tak bych jej asi dodržoval.
což je syntaktická šílenost, ale tak to prostě jeBohuzel ano, typy v C jsou syntakticka (a asi i semanticka) silenost.. Ja osobne bych C nahradil Haskellem, kde by se misto IO vracel stavovy automat (neco jako ST?) nad urcitym typem. A kompilator by proste vytvoril ten stavovy automat a prelozil do assembleru.
Bohuzel ano, typy v C jsou syntakticka (a asi i semanticka) silenost..Ano, je to des. Ale vzhledem k tomu, ze v dobe, kdy vzniklo C, se jeste tapalo v tom, jak vubec spravne udelat proceduralni programovani, nemel bych jim toto zase tak za zle. Na to, ze je C jenom velice mala abstrakce nad instrukcemi procesoru, dopadlo to jeste docela dobre. Treba znam cloveka, ktery si je schopen vystacit se tremi datovymi typy -- int, unsigned long, void *. Proto si dokazu dost dobre predstavit, ze C mohlo skoncit s podobnym typovym systemem.
Ja osobne bych C nahradil Haskellem, kde by se misto IO vracel stavovy automat (neco jako ST?) nad urcitym typem.To je sen kazdeho systemoveho programatora.
Na to, ze je C jenom velice mala abstrakce nad instrukcemi procesoru, dopadlo to jeste docela dobre. Treba znam cloveka, ktery si je schopen vystacit se tremi datovymi typy -- int, unsigned long, void *. Proto si dokazu dost dobre predstavit, ze C mohlo skoncit s podobnym typovym systemem.Tak ono koneckonců BCPL i B měly jen jeden typ - slovo.
Ja osobne bych C nahradil Haskellem, kde by se misto IO vracel stavovy automat (neco jako ST?) nad urcitym typem.Vim zhruba, co je IO monáda, ale úplně nerozumim, jak to myslíš. Můžeš to rozvést?
Vim zhruba, co je IO monáda, ale úplně nerozumim, jak to myslíš. Můžeš to rozvést?No, ja taky presne nevim, jak to myslim.
Tak teoreticky můžeš vzít GraalVM, poslat do něj nějaký vyšší/dynamický jazyk, nechat si ten kód optimalizovat a nakonec přeložit na nativní.
Ale nevím, jestli budou lidi chtít psát tímhle způsobem zrovna operační systémy – tam bych spíš věřil něčemu jako Rust.
Tady je taky trochu problém s cyklickými závislostmi abstrakcí :-) Klasicky se systém staví po vrstvách, od těch nejjednodušších, nejnižších, na kterých se pak budují vyšší. Takže teoreticky jsi schopný postavit počítač z nuly a ze zdrojáků. A teď vlastně přeskočíš několik vrstev abstrakce až k těm nejvyšším a pak to zpátky vracíš na ty nižší, abys z toho měl jádro systému v nativním kódu. Ono je to samozřejmě trochu zacyklené už teď – taky potřebuješ nějaký kompilátor ke zkompilování kompilátoru, kterým zkompiluješ jádro… ale pořád je ta infrastruktura potřebná k zavedení OS ještě relativně jednoduchá. Ale když do toho začneš tahat věci jako kompilování Haskellu (ať už přes Graal nebo jinak), tak se to zacyklení prohlubuje.
Nebo bys uměl napsat kompilátor Haskellu ručně v assembleru? Nevím, možná by to šlo… Tahle implementace by mohla být totálně neoptimalizovaná, protože tímhle kompilátorem bys mohl jen zkompilovat lepší kompilátor, který už by byl psaný ve vyšším programovacím jazyce a mohl by dělat ty potřebné optimalizace.
Ano, tohle v tom Graalu právě můžeš dělat – kompilovat víceméně libovolný jazyk a ve výsledku z toho mít nativní kód, který nepotřebuje runtime/interpret.
Počkat. To AFAIK není pravda - jmenuje se to GraalVM, ne?Ano, tohle v tom Graalu právě můžeš dělat – kompilovat víceméně libovolný jazyk a ve výsledku z toho mít nativní kód, který nepotřebuje runtime/interpret.
GraalVM se dá používat různými způsoby – buď jen jako takové lepší JVM, které umožňuje kombinovat různé jazyky v jednom programu a přidává různé optimalizace… nebo ho můžeš použít právě pro AOT kompilaci, a pak z toho vypadne nativní binárka, která tedy obsahuje něco, co se jmenuje Substrate VM, ale je to něco jiného než JVM nebo GraalVM a dá se na to IMHO dívat spíš jako na standardní knihovnu nebo nějakou sadu funkci. Když zkompiluješ nějaký program v céčku a přilinkuješ k němu staticky nějakou knihovnu, tak to pořád bereš jako nativní binárku, ne? I když ta knihovna bude řešit třeba správu paměti nebo vlákna (lépe než bys to dělal ručně v C).
z toho vypadne nativní binárka, která tedy obsahuje něco, co se jmenuje Substrate VMNo ale ja prave nechci VM.. ja chci pomoci funkcionalniho jazyka specifikovat neco, co pobezi primo na HW. Ja zcela explicitne nechci ten HW abstrahovat. To je ostatne dnes smyslem jazyku jako C. Kdybychom umeli Haskell vzdy kompilovat tak, aby se vyrovnal nativnimu C kodu, tak bychom C nepotrebovali. Ale bohuzel to tak neni a tudiz C (nebo jiny nizkourovnovy jazyk) potrebujeme.
Nerad bych kecal, ale zkus si tu binárku dekompilovat – IMHO to nebude moc daleko od toho, co chceš.
BTW: našel jsem nějaký Haskell pro Arduino: frp-arduino – píší tam: „It compiles to C code“ – tak třeba by šlo něco podobného dělat i na počítači (jestli se to kompiluje do C nebo do nativního kódu/assembleru už je celkem detail).
GraalVM se dá používat různými způsoby – buď jen jako takové lepší JVM, které umožňuje kombinovat různé jazyky v jednom programu a přidává různé optimalizace… nebo ho můžeš použít právě pro AOT kompilaci, a pak z toho vypadne nativní binárka, která tedy obsahuje něco, co se jmenuje Substrate VMAno, ale stále tam nutně přece musí být např. garbage collector a další věci. Jazyky jako Java obecně není možná zkopmilovat zcela bez runtimu z jejich podstaty. To by leda musel být nějaký subset/rozšíření, které by umožňovalo deterministickou správu paměti, mělo by žádnou nebo omezenější reflexi, atd. Čiliže spíš ti z toho Graalu vypadne něco jako jsou binárky programů v Go - tj. nativní binárka, ale obsahující plný runtime vč. garbage collectoru.
Ano, ale stále tam nutně přece musí být např. garbage collector a další věci.
Ono chtít vyšší programovací jazyk a nechtít GC nebo jinou formu automatické správy paměti je trochu oxymóron. Protože ruční správa paměti z principu je nízkoúrovňový opruz.
To by leda musel být nějaký subset/rozšíření, které by umožňovalo deterministickou správu paměti, mělo by žádnou nebo omezenější reflexi, atd.
Tak to v zásadě je. Ne každý program v Javě (nebo jiném jazyce nad JVM/GraalVM) jde přeložit na nativní binárku.
Substrate VM has partial support for reflection and it needs to know ahead of time the reflectively accessed program elements.
Ono chtít vyšší programovací jazyk a nechtít GC nebo jinou formu automatické správy paměti je trochu oxymóron.Dejme tomu, ale chtít vyšší programovací jazyk bez runtime není tak úplně oxymorón - věci jako deterministická správa paměti (Cyclone, Rust, Carp), refcounting (C++, Swift, Rust), QSBR nebo EBR nevyžadují runtime.
Mozna uz neco takoveho je, ale jak (nerad priznavam, ze asi spravne) pise deda.jabko, systemovi programatori (Cckari) se do toho zrovna nehrnou.No, tak svým způsobem něco takového v omezené míře dělá Rust. To, co dělá, bych rozdělil v kontextu vlákna na v podstatě dvě hlavní kategorie: verifikace stavového automatu a generování (korektního) stavového automatu. Verifikace, to je hlavně to, co dělá borrow checker, tzn. napíšeš si kód, který třeba pracuje s nějakým resourcem z jednoho místa a potom za jiných podmínek z jiného, což je v zásadě nějaký stavový automat. A ta verifikace zajišťuje některé invarianty, jako třeba že nepracuješ mutabilně s jedním resource z více míst nebo že s ním nepracuješ mimo dobu života. Generování pak hlavně dělají různé věci vytvořené nad typovým systémem, jako např. cell, Once a pak hlavně async věci (Future, Generator, ...). Ty Futures jsou v podstatě napsané s tou stejnou myšlenkou, jako máš ty, tj. aby to generovalo korektní stavový automat. Ale je to omezené na async věci, nemá to obecné zaměření. Další, co mě napadá, jsou různé generátory parserů / parser frameworky pro různé jazyky. Typicky tam jde vždycky o to samé - generovat korektní stavový automat z reprezentace na vyšší úrovni. Na to by možná taky stálo za to se podívat. Jinak Rust samozřejmě není pure FP jazyk jako Haskell, tj. neposkytuje ty záruky v podobně zajíštění funkcí bez side-effectu. Na druhou stranu ale tam, kde se side effecty dějou, poskytuje AFAIK lepší záruky než Haskell. Např. AFAIK Haskell je o dost náchylnější na race conditions v I/O. Z tohohle důvodu si myslim, že to není jen otázka správného překladu Haskellu, ale bylo by ho potřeba IMHO i rozšířit o chybějící koncepty v type systému, hlavně druhy přístupu v I/O. Alespoň do té míry, jako to má Rust, ideálně i s lepší granularitou. Otázka je, jestli by to pak byl prakticky použitelný jazyk, to je na tom celým to nejtěžší. Rust není úplně vrchol ergonomie a Haskell už vůbec ne
Rust není úplně vrchol ergonomie a Haskell už vůbec neTady asi trochu nesouhlasim. Na Haskellu je ergonomicke, ze se v nem dobre definuji DSL, podobne jako treba v ruznych Lispech a tak (navic typove bezpecne). Ma jednoduchou syntaxi.. takze jsem to psal spis jako reakci na hruzostrasnou (ale historicky pochopitelnou) syntaxi (a semantiku) jazyka C. (Coz je ovsem v praxi vzdy dvojsecne. V principu nic nebrani Haskellistum programovat imperativne, a nebo Lisparum definovat typovou kontrolu, ale v praxi vsechny knihovny s takovym pristupem nepocitaji, takze oboji je pres ruku. Knihovny a vubec cela kultura programovani v danem jazyce je ve finale to, co urcuje ergonomii.) Jestli ma tuhle vlastnost i Rust, nevim.
S tim Rustem mas asi dost pravdu.. Ja Rust neznam, chapu to jako urcity kompromis mezi eleganci a prakticnosti, a z toho mam ponekud obavy, podobne jako treba u Scaly.IMO ta situace je jiná než u Scaly. Scala je prostě mix OOP a FP a to je víceméně všechno. Resp. mají afaik vestavěnej Actor model, ale to je věc specifická pro určitý use-case. Rust je na tom IMO lépe v tom, že přinesl (zpopularizoval, zpřístupnil v praxi, vylepšil) nápady, které do té doby existovaly jen v obskurních jazycích jako Cyclone apod. Z pohledu Rustu je naopak Haskell spíš kompromis, protože víceméně poskytuje pouze rozlišení "funkce má side effect" a "funkce nemá side effect", což nestačí. Rust sice neposkytuje tu úroveň "nemá side effect", ale poskytuje větší paletu v té oblasti "má side effect" - a o to typicky jde v systémovém programování. Snažim se vymyslet, na čem to ilustrovat. Nejjednodušší příklad, co mě napadá, je program, který ze dvou vláken vychrlí na STDOUT nějaké stringy tak, aby se (náhodně) zmatlaly dohromady. V Rustu takový program nepřeložíš, respektive musel bys použít
unsafe
kód, abys tohle mohl udělat. Moje znalost Haskellu je omezená, ale AFAIK tohle tam půjde bez problému (oprav mě, pokud se pletu).
Jinými slovy, monády jsou hezká věc, ale jejich užitečnost je závislá na typovém systému, nad kterým jsou postavené.
Tady asi trochu nesouhlasim. Na Haskellu je ergonomicke, ze se v nem dobre definuji DSL, podobne jako treba v ruznych Lispech (navic typove bezpecne)Použil jsem špatný výraz - místo ergonomie jsem měl spíš napsat něco jako "učící křivka", která je u obou dost strmá. Co se týče typové bezpečnosti, Rust mi pro systémové programování, kde se nelze vyhnout side effectům, přijde typově bezpečnější než Haskell, viz výše, ačkoli samozřejmě je to sporné a záleží, co kdo považuje za typovou bezpečnost. Co se týče vytváření DSL, v tom budou Haskell a Lisp určitě lepší. Metaprogramování je v Rustu relativně omezené. Jinak Rust určitě nemá v oblasti bezpečných systémových jazyků první ani poslední slovo. Například ta granularita druhů referencí by mohla být širší (a taky se příležitostně rozšiřuje). Například immutabilní reference obecně slouží ke skutečně immutabilnímu přístupu, ale v některých výjimkách slouží k 'bezpečnému' (non-racy) mutabilnímu přístupu, tzv. interior mutability (například atomické nebo jiné specielní typy) - z teoretického hlediska by bylo lepší, aby pro tohle existoval samostatný typ reference. Možná by tě v tomhle kontextu mohl zajímat experimentální jazyk Carp - je to Lisp se statickými typy a s ownership trackingem jako má Rust. Nemá GC. Je napsaný v Haskellu, btw
int **a[m][n];
, tak jsem deklaroval pole polí pointerů na pointry na int. Nejdřív se přečtou obě hranatice a pak teprve obě hvězdičky.
Z toho důvodu taky nevidím žádnou syntaktickou šílenost, naopak vidím jasný důvod, proč psát hvězdičku k názvu proměnné a nikoliv k identifikátoru základového typu.
naopak vidím jasný důvod, proč psát hvězdičku k názvu proměnné
Moc nechápu, jak to vyplývá z toho, co jsi napsal.
Asi vnímá tu hvězdičku jako unární operátor podobně jako při dereferenci.
To mi přijde spíš jako mnemotechnická pomůcka ve smyslu: „když napíšu int *x;
a pak někde jinde *x
, tak na tom místě dostanu int
, což funguje obdobně, jako kdybych udělal int x;
a pak psal x
, tak pak mi to x
dá taky int
.“ Tzn. „Deklaruji xxx zzz;
a pak v kódu píšu zzz
a dostávám tam typ xxx
(a jestli byla součástí zzz nějaká hvězdička mě nezajímá, píšu to i s ní, jako by to byla součást názvu proměnné).“
Mně to tam ale nesedí – jednak nevím, co bych tou hvězdičkou dereferencoval, na co bych ten unární operátor aplikoval, když to x
v tu chvíli ještě neexistuje – teprve ho na tom řádku deklaruji. A jednak ukazatel vnímám jako datový typ – číslo, offset, adresu v paměti. To mi přijde jako zásadnější informace, než, typ, který by se na té adrese měl nacházet – ostatně ty ukazatele jdou přetypovat a ten kus paměti interpretovat jinak, než jak se používal původně – tzn. tohle se může změnit, ale ukazatel/offset/adresa je to pořád.
Mně to tam ale nesedí – jednak nevím, co bych tou hvězdičkou dereferencoval, na co bych ten unární operátor aplikoval, když to x
v tu chvíli ještě neexistuje – teprve ho na tom řádku deklaruji. A jednak ukazatel vnímám jako datový typ – číslo, offset, adresu v paměti. To mi přijde jako zásadnější informace, než, typ, který by se na té adrese měl nacházet – ostatně ty ukazatele jdou přetypovat a ten kus paměti interpretovat jinak, než jak se používal původně – tzn. tohle se může změnit, ale ukazatel/offset/adresa je to pořád.
Což o to, přetypovat můžeš i ten ukazatel jako takovej (existují na to i typy - uintptr_t
, ptrdiff_t
, ...).
Jinak ale nepřijde mi, že by z té argumentace jakkoli plynulo, že by ta hvězdička měla být nalevo u dereferencovaného typu, i když ji bereš jako součást typu. Jak už jsem napsal, C/C++ není jako Pascal/Rust/Swift, kde máš striktně na jedné straně ident a na druhé typ. V C/C++ je ten typ všude možně okolo toho identu. A kolega 'random' má recht v tom, že se to vyhodnocuje jako výraz, byť fyzicky se v té chvíli dereference neděje...
a & b == c | d
se prostě někdy budou muset závorkovat, podle toho, co chce autor napsat), ale fakt stačí naučit se je jednou. Spirálové pravidlo je matoucí a špatné.
Spirálové pravidlo je matoucí a špatné.No tak ho nepoužívej, když se ti nelíbí, já do toho nikoho nenutim... Já si tu prioritu operátorů chronicky pamatuju blbě (navíc v každým jazyce to je trochu jinak), takže mi to pomáhá...
ale přijde mi lepší psát hvězdičku k typu než k názvu proměnné. Chápu, že i u toho názvu dává svým způsobem smysl, ale myslím si, že víc patří k typu (proměnná je typu ukazatel).
int *p;
p
je typu ukazatel na int. Říkám tím že když někde v kódu použiju *p
tak výsledek bude typu int... Což samozrejmě implikuje že p je ukazatel na int p
pokud napíšu:int (*p)[4];
(*p)[0..3]
tak musím dostat int, takže p
je ukazatel na pole 4 intů int *p[4];
int *(p[4]);
Toto int *p[4];
je odkedy pole 4 ukazatelů na int.
?
Spravne je:
int *p[4];
je definicia pointer na pole 4 prvkov.
A ked uz tak, tak:
char *p[]
je rovne char **p.
Cize p[] sa preklada za *p.
Ako spracuvavas parametre argv funkcie main?
Spravne je: int *p[4];
je definicia pointer na pole 4 prvkov.
Ne, není. Předřečník to napsal správně. Viz též tady.
Pouze v některých kontextech, ne ve všech (např. ne pro operátorchar *p[]
je rovnechar **p.
Cize p[] sa preklada za *p.
sizeof
). Viz příklad.
int a[4][5];
je neco podstatne jineho nez: int **a;
int f(){
char *a = "AaaaaaaA";
char b[] = "Bleeeeeeeeeee";
}
gcc -O0 -S test.c
To ze "je to to samy" a muzes napsat a=b je spis takovy postrani efekt.
#include <stdio.h> void vypis_2_znak(char *text) { if(text == NULL) return; if(*text != '\0') if(*(text+1) != '\0') // alebo if((text[1]) != '\0') putchar(*(text+1)); } int main(void) { char text[]="Jedne dva tri"; char *meno="Dnes"; vypis_2_znak(text); vypis_2_znak(meno); putchar('\n'); return 0; }
int a[4][5] - int **a; Obidve sa pouziva na 2-rozmerne pole. Ak si si alokoval dynamicky, vedel by si, ze by si pouzival 2-nasobny pointer.No sebevedomi ti evidentne nechybi. Ted jeste kdyby to bylo podlozene znalostma
a[4][5]
je cele ulozene v jednom bloku pameti. To znamena ze mam pole o 4 prvcich, a kazdy prvek je, ne ukazatel, ale pole o 5 prvcich.
Kdyz budu pristupovat na index a[i][j]
, tak se vezme pocatecni adresa a
a poskoci se o 5*i + j
prvku.
Zatimco u **a
se pri pristupu na index a[i][j]
nejdriv nacte ukazatel z a[i], udela se dereference a tou se skoci na nejakou jinou adresu z ktere se nacte prvek j.
V C se pole a ukazatel da na spouste mist pouzivat stejnym zpusobem a tak si hodne lidi mysli ze je to totez ale neni. Preloz si to do asm a uvidis ze v jednom pripade je tam nasobeni a v druhem dereference Radu použít věci jako bzero, či memset na vymazání paměti ignoruj. Stejně je tam pak race-condition mezi malloc a memset, takže je to k ničemu. Pokud bys psal nějaký citlivý sw, tak musíš použít funkci OS, co to udělá atomicky.Race condition mezi čím a čím? Ie. mezi alokujícím vláknem a jiným, nebo mezi procesy? Pomůže
calloc()
?
Programovat v C takovéto pole je hovadina. Prostě použij standardni pole a neschovávej jeho typ pod nějaký stupidní objekt, co je napodobeninou šablon z C++, ale bez typové informace. Pokud něco takového potřebuješ, tak používáš špatný jazyk.No nicméně větší codebases v C takovéhle všelijaké utility mívají. Linux má
flex_array
, kluci z matfyzu mají takové věci v té jejich libucw
, v Solarisu blahé paměti jsme mívali něco podobného + typově bezpečný spojový seznam pomocí maker...
Race condition mezi čím a čím? Ie. mezi alokujícím vláknem a jiným, nebo mezi procesy? Pomůže calloc()?IMHO je to nesmysl. Pokud mas jednovlaknovou aplikaci, tak ti race-condition z podstaty nehrozi. Pokud bys mel vicevlaknovou aplikaci, tak by se druhe vlakno muselo dostat k odkazu na onu nevynulovanou pamet. Bud to udelas vedome, pak je to tvoje chyba. Pokud to dokaze jine vlakno bez vedomi programatora, tak mas v programu vetsi chybu, ktera ti patrne umozni cist cokoliv, co mas v pameti, takze nema cenu to resit. Calloc ti nepomuze, je to v user space. Jardikova rada pouzit volani jadra neni uplne stastny napad. Muzes treba udelat mmap(/dev/zero), ale tim ziskas pamet zarovnanou na jednotlive stranky, coz aplikacniho programatora opravdu potesi. Nemluve o rychlosti.
To neni pravda, race condition neni sice mezi vlakny te tvoji aplikace, ale pamet chces mit asi vynulovanou,Kdyz tam neni race-condition mezi vlakny toho jednoho procesu, tak mezi cim?
pamet chces mit asi vynulovanou, aby ti nekdo necetl informace, treba stara data v pameti.Ke starym datum jineho procesu nemas prilezitost se dostat. Alokator muze bud pouzit sbrk nebo mmap+MAP_ANONYMOUSE nebo mmap+/dev/zero, aby ziskal pamet, kterou pak prerozdeli mallocem, ale vzdy je ta pamet vynulovana.
mlock()
nebo něco podobnýho, aby se ty data neodswapovaly na disk.
Třeba botan má na tohle specielní vector typ.
Kdyz tam neni race-condition mezi vlakny toho jednoho procesu, tak mezi cim?Mezi tvoji aplikaci a jinou aplikaci, ktera muze mit pristup do pameti tve aplikace a mezi alokaci a vynulovanim ti ji muze precist.
Alokator muze bud pouzit sbrk nebo mmap+MAP_ANONYMOUSE...To je mozne, ale alokator muze vyuzit nejakou "uvolnenou" pamet, kterou si nekde skladuje pro znovuvyuziti, a vubec o ni system nemusi zadat. Dale si nejsem jisty, jestli ti mmap nemuze vratit nejakou starou stranku, kdyz treba pred tim provedes unmap. Protoze se stale jedna o puvodni proces, system ji asi nemusi prepsat nulama. Nikde jsem to v manualovych strankach nevidel garantovano, ze opravdu je garantovano, ze pamet je vynulovana. (Pokud me na to odkazes, rad si to prectu a doplnim si znalosti). A jaka je situace na Windows, to take nevim. Obecne ale vymazavat pamet po alokaci nebo po pouziti mi prijde jako zbytecny pocit bezpecnosti, ktery tam neni, protoze je tam ta race-condition, kdy mezi tim se muze stat cokoliv (i kdyz ta doba ke zneuziti je treba mala).
Mezi tvoji aplikaci a jinou aplikaci, ktera muze mit pristup do pameti tve aplikace a mezi alokaci a vynulovanim ti ji muze precist.To je nesmysl. Abys mel mezi dvema procesy sdilenou pamet, ktera je i v rezimu pro zapis (aby hrozil race condition), tak se musis docela snazit, minimalne natolik, aby sis vsiml, ze hrozi nejaky soubeh. Neni to vec, kterou udelas omylem pomoci malloc a free.
To je mozne, ale alokator muze vyuzit nejakou "uvolnenou" pamet, kterou si nekde skladuje pro znovuvyuziti, a vubec o ni system nemusi zadat.Ano, to muze. Ale pak je to pamet/data, ktera patri tomu procesu, a pokud muzes ziskat pristup k teto pameti, je to jen v dusledku nejake dalsi hlubsi chyby v tom programu. (Uz jsem to rozebiral vys, nebo niz, ted nevim.)
Dale si nejsem jisty, jestli ti mmap nemuze vratit nejakou starou strankuPokud udelas mmap(/dev/zero), docela by me prekvapilo, kdyz by se do pameti dostalo i neco jineho. Pokud udelas mmap(MAP_ANONYMOUS) mely by tam byt nuly. MAP_ANONYMOUS The mapping is not backed by any file; its contents are initialized to zero.
Nikde jsem to v manualovych strankach nevidel garantovano, ze opravdu je garantovano, ze pamet je vynulovana.Ja bych toto videl jako vlastnost kazdeho pricetneho operacniho systemu, jinak by tu byly zastupy utoku typu heartbleed.
A jaka je situace na Windows, to take nevim.Taky presne nevim, ale predpokladal bych to same. Uz jen z toho duvodu, ze "proces necinne procesy" (nebo jak se to jmenuje) ma prave za ukol nulovat stranky pro dalsi pouziti.
obecne ale vymazavat pamet po alokaci nebo po pouziti mi prijde jako zbytecny pocit bezpecnosti, ktery tam neni, protoze je tam ta race-condition, kdy mezi tim se muze stat cokoliv (i kdyz ta doba ke zneuziti je treba mala).Spis to vidim, ze ztrasis bubakem, ktery neexistuje. Nebo zkus popsat, jak by ten race-condition mohl vzniknout.
pamet chces mit asi vynulovanou, aby ti nekdo necetl informace, treba stara data v pametiTak u pameti co dostanes od OS by mel zaridit OS, aby neobsahovala data jinych procesu. Jinak obecne - vi nekdo jak se v Jave (nebo jinych managed jazycich) resi citliva data? Ja jsem chtel kdysi davno v .NETu nejak zaridit, aby se heslo po pouziti vymazalo z pameti, ale jelikoz to slo pres nejaky UI dialog, bylo to prakticky nemozne dosahnout.
V Javě se běžně místo String
u např. pro hesla používá char[]
, který si po použití vynuluješ. Případně jsou nějaké knihovny na práci s bezpečnou pamětí… nebo jsem viděl i aplikaci, která přes nativní API komunikovala s klíčenkou v KDE.
To je dost obezlicka, ale hlavne, pokud se v tom angazuji jine knihovny (treba GUI), tak nikdy nemas jistotu, ze se to nekde po ceste nezkopirovaloTo nemas nikdy.
(a tudiz nezustalo v managed pameti)To je spis teoreticky problem, protoze pristup k managed pameti mas docela dobre kontrolovany a nenapad me zpusob, jak by mohla data uniknout (nejak bokem). Horsi je, kdyz ti data zustanou v unmanaged pameti, tam je prostor pro chyby a potencialni uniky mnohem vetsi.
Proto i ten GUI prvek JPasswordField má metodu public char[] getPassword()
s popisem:
Returns the text contained in this TextComponent. If the underlying document is null, will give a NullPointerException. For stronger security, it is recommended that the returned character array be cleared after use by setting each character to zero.
Je tam snaha, aby se to rozkopírovalo na co nejméně míst a aby se to co nejdřív promazalo. Nicméně vždycky tam bude alespoň na chvíli nějaké místo, ze které by šlo ten citlivý údaj ve vhodnou chvíli ukrást. Něco jde řešit přes TPM, ale pokud tam má být nějaká interakce s uživatelem nebo se má třeba to heslo použít pro přihlášení někam na server, tak přes tu „nebezpečnou“ paměť stejně musí projít.
Jak se to řeší jinde? Taky promažeš pole, ne? Maximálně ho umístíš do nějaké bezpečné části paměti, která se neodswapuje na disku (nicméně kompletně šifrovaný disk je lepší mít tak jako tak).
No nicméně větší codebases v C takovéhle všelijaké utility mívají. Linux má flex_array, kluci z matfyzu mají takové věci v té jejich libucw, v Solarisu blahé paměti jsme mívali něco podobného + typově bezpečný spojový seznam pomocí maker...Aneb, kdo nepochopil C++, je odsouzen si znovu-vynalézt vlastní.
Treba jako v Haskellu, typy zacinaji velkym, hodnoty jsou malymTak to ma i Java a cte se to docela dobre.
A je nejaka dobra konvence (v C) jak pojmenovavat typy?Rekl bych, ze nejaka univerzalni konvence neexistuje. Pamatuju si, ze uz v davnych devadesatych letech se pascalisti posmivali ceckarum, ze nemaji jednotnou konvenci pro pojmenovani typu a kazdy si to masti, jak se mu zlibi. Pro ty pozdeji narozene, v Pascalu se pouzivala konvence
TData
(pro oznaceni typu) a PData
(pro ukazatel na hodnotu typu TData).
Maďarská notace? :-) Ale v mnoha jazycích není potřeba, protože čím víc ti toho kontroluje kompilátor (a např. napovídá IDE), tím méně toho potřebuješ nacpat do názvu proměnné.
Maďarská notace?Madarske notace je neco trochu jineho. Tady resime, jak v kodu odlisit jednotlive elementy jazyka, typ, hodnota, procedura. Madarska notace resi, jak pojmenovavat promenne (a pripadne typy). A to jeste ve dvojim smyslu. Puvodni Simonyiova myslenka byla, ze se u jednotlivych promennych bude uvedeny prefix indikujici jaky "typ" hodnoty v promenne je. Napr. cntItems (pocet polozek), rwIndex (index radku), atp. Coz dava smysl, protoze to programatora navede, co v te promenne/argumentu muze cekat. Jenomze ostatni v Microsoftu si vyznam slova typ vylozili, ze ten prefix ma indikovat "datovy typ", a totalne se to zvrhlo do uplnych zverstev, ktere bezhlave kopirovali dalsi lidi. Jednak programator v podstate supluje praci prekladace, kdy uvadi nejenom typ promenne, ale jeste to kopiruje do jejiho nazvu. Tato informace je pro programatora stejne k nicemu, ale navic ti typy prestanou fungovat jako abstrakce. V momente, kdy se rozhodnes zmenit typ hodnoty treba z HWND na LP, musis menit vsude i nazvy promennych, misto toho, abys jen zmenil strukturu toho typu.Ale v mnoha jazycích není potřeba, protože čím víc ti toho kontroluje kompilátor (a např. napovídá IDE), tím méně toho potřebuješ nacpat do názvu proměnné.
Tak me treba prijde ta konvence *_t jako docela pekna, ale bohuzel, jak pise Jardik, POSIX si to zabral pro sebe.To si sice zabral, nicméně lidi to celkem často používaj. Správně to striktně vzato není, nicméně dokud si daný název POSIX fakt pro něco nealokuje, tak to v zásadě v praxi nevadí. Což když třeba má člověk v tom typu svoje věci, není ani moc pravděpodobné.
Ale je pravda, ze Javova konvence by se vlastne v C dala dobre pouzit, a zhruba odpovida te Haskellovske.. Nikdy me to nenapadlo, ale dava to perfektni smysl (az tedy na ten POSIX..).No, třeba ten Rust používá zásadně konvenci
PascalCase
pro typy (a enum varianty), snake_case
pro funkce, proměnné atp. a UPPER_SNAKE_CASE
pro globály a globální konstanty.
Některé C/C++ codebases používají tenhle style taky, například IIRC GTK.
No, třeba ten Rust používá zásadně konvenci PascalCase pro typy (a enum varianty), snake_case pro funkce, proměnné atp. a UPPER_SNAKE_CASE pro globály a globální konstanty.
Je nějaký racionální důvod pro ten „snake_case pro funkce“? Zvažoval se při návrhu jazyka/konvencí camelCase? Schválně by mě zajímalo, jaké byly argumenty pro a proti…
Mě na tom přijde zvláštní, že používají (k vyznačení hranice slov) jak podtržítko tak změnu velikosti písmen. Jinde si většinou vybrali buď jedno nebo druhé. Podtržítka jsou typičtější pro jazyky, které nerozlišují velikost písmen a kde by se ty hranice ztrácely (i kdybys napsal MojeProměnná
, tak by se to nakonec převedlo na mojeproměnná
, takže místo toho radši píšeš moje_proměnná
). To je případ třeba SQL (kde bys to sice mohl obejít pomocí "MojeProměnná", ale komu se chce psát všude uvozovky, že? To už je lepší tam dávat podtržítka). A pokud se někde kombinuje oboje, tak k tomu bývá právě nějaký důvod – jako např. u konstant v Javě, které se píší velkými písmeny, takže u nich jsou podtržítka nutnost, i když zbytek jazyka používá camelCase/PascalCase.
Rozdíl je v tom, že v Javě se konstanty píší VELKÝMIPÍSMENY
, proto má opodstatnění narušit tu jednotnost (všude jinde je camelCase/PascalCase resp. nejsou tam podtržítka) a dát do nich podtržítka → VELKÝMI_PÍSMENY
. Zatímco u těch názvů funkcí v Rustu mi ten důvod jaksi chybí, protože místo moje_funkce()
mohlo klidně být mojeFunkce()
, ne?
Nebo bylo cílem to odlišit od proměnných? Mohlo by se to někdy plést?
(jinak je to tedy celkem nepodstatná blbost, jen mě to zaujalo :-)
proto má opodstatnění narušit tu jednotnostNo tak jak to chápu já tak ta jednodnost je narušená právě proto, aby to bylo poznat, v tom vidím celý smysl toho mít jinej case pro různý věci... Ale možná za tím je něco hlubšího, nevim ¯\_(ツ)_/¯
Nebo bylo cílem to odlišit od proměnných? Mohlo by se to někdy plést?Ne, proměnné a funkce jsou oboje snake_case ...
kterou bohuzel nejde jinde pouzit
Třeba v XML to lze :-) a i se to používá (jestli myslíš pomlčky).
vezmi z kazdeho slova prvni pismeno, spoj je dohromady a pokud takovych identifikatoru potrebujes vic, ocisluj je
To už tam rovnou můžeš dávat prvních pár bajtů z SHA hashe v HEX – vyjde to zhruba nastejno – taky je to jednosměrná funkce, které zpátky nikdo jen tak nedekóduje :-)
Zvažoval se při návrhu jazyka/konvencí camelCaseU Ruby třeba psali, že se camelCase nedoporučuje, protože lidem, co nejsou kovaní v používání latinky (hlavní vývojáři jsou Japonci), se to blbě čte.
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.