Jakie są niektóre koncepcje / techniki / funkcje językowe, o których każdy przyzwoity programista C powinien wiedzieć / być świadomym (wyłącz ogólną inżynierię oprogramowania i podobne i skupiaj się tylko na specyficznych rzeczach C). Chciałbym wiedzieć, żebym mógł uzupełnić niektóre możliwe luki w mojej wiedzy o C.
Zacznij od pytań C dotyczących przepełnienia stosu i sprawdź, czy jest coś, czego nie wiesz.
chrisaycock
3
Programista AC prawdopodobnie powinien to wiedzieć2 + 2 = 4
Edward Strange
21
Powinni wiedzieć o sklepie, który sprzedaje obuwie kuloodporne.
Adam Crossland,
1
Istnieją setki książek na ten temat. Twoje pytanie jest naprawdę dość niejasne. Musisz być bardziej konkretny, aby uzyskać przyzwoite odpowiedzi, które nie są tylko listą rzeczy. Biorąc pod uwagę, że odpowiedzi były tak dalekie od tego pytania, pomyślałbym, że należy je przerobić lub zamknąć.
Walter
2
Inny język programowania?
Muhammad Alkarouri
Odpowiedzi:
19
Specyficzny dla C? Oprócz standardowych konstrukcji wspólnych dla większości języków proceduralnych, muszę powiedzieć:
(ab) przy użyciu preprocesora
linker vs kompilator
Wskaźniki Wskaźniki Wskaźniki!
Jak tablice są wskaźnikami, są tablicami
Jak działają ciągi C i jak są one również wskaźnikami i tablicami
Jak złe użycie łańcucha C może spowodować przepełnienie bufora
Jak rzucić cokolwiek na cokolwiek (to wszystko to tylko 1 i 0 w końcu :))
Ręczne zarządzanie pamięcią malloc / free
Stack vs Heap
Alias wskaźnika, (dlaczego jest nielegalny w C99)
Myślenie o rozwoju w kategoriach modułów (plików .h / .c) z zestawem publicznie dostępnych funkcji zamiast ściśle klas
Programista AC powinien znać ... inne języki! ;-) Zawsze dobrze jest znać pojęcia z innych języków różnych paradygmatów, takich jak OOP, programowanie funkcjonalne i tak dalej.
Co więcej, spojrzenie na zaciemniony konkurs programistyczny jest zabawny i, co ciekawe, również dobre doświadczenie.
Wspomniałem o „przepełnieniu bufora” w komentarzu do odpowiedzi Pythagrasa, prawdopodobnie powinienem wyjaśnić, co miałem na myśli. W języku C nie wystarczy wiedzieć, że bezpośrednia praca z pamięcią jest niebezpieczna - należy również zrozumieć dokładne sposoby jej niebezpieczeństwa. Naprawdę nie podoba mi się metafora „strzelania sobie w stopę” dla wszystkich tych przypadków - przez większość czasu to nie ty pociągasz za spust, ale często jest to aktor o interesach sprzecznych z twoim i / lub użytkownikami ” .
Na przykład w architekturze ze stosem malejącym (najpopularniejsze architektury pasują do tego rachunku - ogólnie x86 i ARM), gdy wywołasz funkcję, adres zwrotny funkcji zostanie umieszczony na stosie po zmiennych lokalnych zdefiniowanych w parametrze treść funkcji. Jeśli więc zadeklarujesz bufor jako zmienną lokalną i udostępnisz tę zmienną światu zewnętrznemu bez sprawdzania przepełnienia bufora, to tak:
void myFn(void){char buf[256];
gets(buf);}
zewnętrzny użytkownik może wysłać ci ciąg znaków, który zastępuje adres zwrotny ze stosu - w zasadzie może zmienić pomysł programu na graf wywołań, który prowadzi do bieżącej funkcji. Użytkownik podaje więc ciąg znaków, który jest binarną reprezentacją kodu wykonywalnego dla twojej architektury, wystarczającą dopełnieniem, aby przepełnić stos myFn, oraz dodatkowe dane, aby zastąpić adres zwrotny w myFncelu wskazania kodu, który ci dał. Jeśli tak się stanie, to kiedy myFnzwykle zwróci kontrolę nad swoim abonentem wywołującym, zamiast tego rozgałęzi się do kodu dostarczonego przez złośliwego użytkownika. Jeśli napiszesz kod C (lub C ++), który może być narażony na zaufanie niezaufanych użytkowników, musisz zrozumieć ten wektor ataku. Powinieneś zrozumieć, dlaczego przepełnienie bufora w stosunku do stosu jest często (ale nie zawsze) łatwiejsze do wykorzystania niż w przypadku stosu, i powinieneś zrozumieć, w jaki sposób układana jest pamięć w stosie (niekoniecznie zbyt szczegółowo, ale pomysł, że w danym malloc()regionie znajdują się otaczające go struktury kontrolne, może pomóc zrozumieć, dlaczego twój program ulega awarii w innym malloc()lub w free()).
C udostępnia ci szczegółowe informacje o tym, jak działa twój komputer, i daje ci większą bezpośrednią kontrolę nad twoim komputerem, niż jakikolwiek inny język edytowany przez użytkowników w powszechnym użyciu. Z wielką mocą wiąże się wielka odpowiedzialność - tak naprawdę musisz zrozumieć te szczegóły niskiego poziomu, aby bezpiecznie i skutecznie pracować z C.
2 + 2 = 4
Odpowiedzi:
Specyficzny dla C? Oprócz standardowych konstrukcji wspólnych dla większości języków proceduralnych, muszę powiedzieć:
źródło
Zrozumienie wskaźników, a zrozumiesz komputery.
źródło
Oprócz doskonałej odpowiedzi pythagras,
jak pisać (lub przynajmniej czytać) skomplikowane deklaracje, takie jak
char (*(*funcs[4])())[10]
funcs jest tablicą [4] wskaźników do funkcji zwracającej wskaźnik do tablicy [10] char
źródło
źródło
Programista AC powinien znać ... inne języki! ;-) Zawsze dobrze jest znać pojęcia z innych języków różnych paradygmatów, takich jak OOP, programowanie funkcjonalne i tak dalej.
Co więcej, spojrzenie na zaciemniony konkurs programistyczny jest zabawny i, co ciekawe, również dobre doświadczenie.
źródło
Wspomniałem o „przepełnieniu bufora” w komentarzu do odpowiedzi Pythagrasa, prawdopodobnie powinienem wyjaśnić, co miałem na myśli. W języku C nie wystarczy wiedzieć, że bezpośrednia praca z pamięcią jest niebezpieczna - należy również zrozumieć dokładne sposoby jej niebezpieczeństwa. Naprawdę nie podoba mi się metafora „strzelania sobie w stopę” dla wszystkich tych przypadków - przez większość czasu to nie ty pociągasz za spust, ale często jest to aktor o interesach sprzecznych z twoim i / lub użytkownikami ” .
Na przykład w architekturze ze stosem malejącym (najpopularniejsze architektury pasują do tego rachunku - ogólnie x86 i ARM), gdy wywołasz funkcję, adres zwrotny funkcji zostanie umieszczony na stosie po zmiennych lokalnych zdefiniowanych w parametrze treść funkcji. Jeśli więc zadeklarujesz bufor jako zmienną lokalną i udostępnisz tę zmienną światu zewnętrznemu bez sprawdzania przepełnienia bufora, to tak:
zewnętrzny użytkownik może wysłać ci ciąg znaków, który zastępuje adres zwrotny ze stosu - w zasadzie może zmienić pomysł programu na graf wywołań, który prowadzi do bieżącej funkcji. Użytkownik podaje więc ciąg znaków, który jest binarną reprezentacją kodu wykonywalnego dla twojej architektury, wystarczającą dopełnieniem, aby przepełnić stos
myFn
, oraz dodatkowe dane, aby zastąpić adres zwrotny wmyFn
celu wskazania kodu, który ci dał. Jeśli tak się stanie, to kiedymyFn
zwykle zwróci kontrolę nad swoim abonentem wywołującym, zamiast tego rozgałęzi się do kodu dostarczonego przez złośliwego użytkownika. Jeśli napiszesz kod C (lub C ++), który może być narażony na zaufanie niezaufanych użytkowników, musisz zrozumieć ten wektor ataku. Powinieneś zrozumieć, dlaczego przepełnienie bufora w stosunku do stosu jest często (ale nie zawsze) łatwiejsze do wykorzystania niż w przypadku stosu, i powinieneś zrozumieć, w jaki sposób układana jest pamięć w stosie (niekoniecznie zbyt szczegółowo, ale pomysł, że w danymmalloc()
regionie znajdują się otaczające go struktury kontrolne, może pomóc zrozumieć, dlaczego twój program ulega awarii w innymmalloc()
lub wfree()
).C udostępnia ci szczegółowe informacje o tym, jak działa twój komputer, i daje ci większą bezpośrednią kontrolę nad twoim komputerem, niż jakikolwiek inny język edytowany przez użytkowników w powszechnym użyciu. Z wielką mocą wiąże się wielka odpowiedzialność - tak naprawdę musisz zrozumieć te szczegóły niskiego poziomu, aby bezpiecznie i skutecznie pracować z C.
źródło
Oprócz innych dobrych odpowiedzi chciałbym dodać do listy techniki programowania obronnego .
Np. Używanie stwierdzeń na początku / na końcu funkcji do weryfikacji kontraktu.
źródło