Jak niektórzy moi czytelnicy zapewne pamiętają, w sierpniu ubiegłego roku w magazynie Programista ukazał się mój artykuł pt. "Diabeł tkwi w szczegółach: C/C++ (część 1)". Dzisiaj zdecydowaliśmy się, wspólnie z redakcją, udostępnić go za darmo w formie PDF (link poniżej). Dodatkowo, redakcja Programisty przygotowała pewien bonus gdyby ktoś z moich czytelników skusił się na prenumeratę.

PDF: Programista_2012_C_CPP_cz_1.pdf (2 MB)

Wstęp do artykułu:
"Programując w językach C lub C++, bardzo łatwo jest popełnić błąd. Powodów można wymienić kilka. Po pierwsze, oba języki są stosunkowo niskopoziomowe, a więc wymagają ręcznego zarządzania pamięcią dynamiczną, dbania o poprawność wskaźników, jak i dbania o wiele innych aspektów. Po drugie, nie wszystkie zachowania obu języków są w pełni zdefiniowane - standardy C i C++ wskazują zachowania nie zdefiniowane w ogóle (ang. Undefined Behavior, dalej UB), nie zdefiniowane w standardzie, ale definiowane przez konkretne implementacje (ang. Implementation-Defined Behavior), oraz takie, które mogą zostać zaimplementowane na więcej niż jeden sposób, ale niekoniecznie muszą zostać jawnie zdefiniowane w dokumentacji (ang. Unspecified Behavior). Niniejszy artykuł ma na celu przedstawienie czytelnikowi kilku przykładów, w których pomimo iluzorycznej trywialności kodu, znajdują się subtelne błędy lub niejasności."

Dodatkowo - wspomniany bonus dla osób zainteresowanych prenumeratą (ograniczony czasowo do końca lutego):

Prenumerata magazynu Programista w wersji drukowanej + 3 dowolne archiwalne wydania w wersji elektronicznej gratis. Po zakupie napisz do nas: admin@programistamag.pl, które wydania wybierasz i od którego numeru zaczyna się prenumerata. Dodatkowo otrzymasz dowolne szkolenie z www.devcastzone.com.

W prenumeracie będziesz też dostawał wydania elektroniczne.

Click aby zamówić prenumeratę.

Ah, i jeszcze na koniec dodam, że w najnowszym numerze Programisty (2/2013 (09)) znalazł się artykuł j00ru o AddressSanitizer - świetnym narzędziu do wykrywania błędów pamięci. Polecam oba (i artykuł, i AddressSanitizer) czytelnikom zainteresowanym zarówno security (kapitalna sprawa przy fuzzingu) jak i programowaniem (pomaga znaleźć te wredne heap-based buffer-overruny objawiające się zazwyczaj Heisenbugami).

I tyle,

Comments:

2013-02-24 18:45:36 = lochtcant
{
Hej ;)

Miły gest od redakcji Programisty, szkoda tylko że ja na swoja prenumeratę muszę jeszcze poczekać :)

Art przeglądnąłem powierzchownie póki co (znajdę na niego czas na pewno), ale rzuciło mi się w oko zdjęcie na końcu artykułu. Jakbym nie wiedział kto to jest Gynvael Coldwind, to patrząc na samo zdjęcie - powiedziałbym że to pisarz jakiś ;p
}
2013-02-25 13:14:23 = Sopelek
{
@lochtcant
No, pisarz w pewnym sensie również ;]
}
2013-02-25 16:20:26 = Marszal
{
Akurat mam ten numer magazynu u siebie ;)
Artykuł bardzo przyjemnie się czyta ;)

Pozdrawiam
}
2013-02-25 18:25:30 = faust1002
{
Przeczytałem artykuł i z przykrością muszę przyznać, że nie ma w nim nic odkrywczego. Wręcz przeciwnie, wszystko co nawet średnio ogarnięty student II roku wie. IHMO przydało by się kilka wpisów dla profesjonalistów, którzy programują zawodowo w C++ od kilku lat.

Ale za to fotka sweetaśna.
}
2013-02-25 20:16:31 = Gynvael Coldwind
{
@lochtcant
Pisarz? Uh huh ;)

@Marszal
:)

@faust1002
Ad meritum - Ah, w końcu mogę sobie podyskutkować! :)
W sumie poruszyłeś 3 sprawy do których się odniosę osobno:
1. "nic odkrywczego"
2. "średnio ogarnięty student II roku [to] wie"
3. "przydało by się kilka wpisów dla profesjonalistów, którzy programują zawodowo w C++ od kilku lat.

Ad 1- Zgoda. Osobiście rozróżniam dwa typy artykułów/publikacji:
- rozszerzające current state of art (czyli publikacje wyników badań, nowych odkryć, wynalazków, etc)
- oraz podnoszące common knowledge (np. tutoriale, artykuły popularno naukowe, etc) - czyli upowszechniające pewne informacje
Magazyn Programista jest raczej miejscem do publikowania artykułów tego drugiego rodzaju, stąd też i mój artykuł nie jest odkrywczy :)
Ba, nie ma tam nic, co by nie było napisane w standardach/draftach C/C++. Różnica polega na tym, że jest to (a przynajmniej miało być) napisane w bardziej przystępnym języku. I tak właśnie miało być :)

Ad 2 - Jak ja bym chciał, żeby było tak jak piszesz! Niestety, z mojej wynika, że tak nie jest. Nie mam twardych statystyk na potwierdzenie tego oczywiście (ale jak rozumiem, i ty nie masz ;>), natomiast weźmy np.:
http://gynvael.vexillium.org/ext/ffmpeg_log.txt
Z tego co widzę jest tam 21 fixów na integer overflowy (jedna z rzeczy opisanych w arytkule). Średnio ogarnięci studenci drugiego roku raczej kodeków nie implementują, więc pytanie brzmi - skąd tam te błędy, jeśli jest tak jak mówisz? :)

Ad 3 - Polecam artykuł j00ru z najnowszego numeru, w którym opisuje działanie "plugina" do LLVM do znajdowania pewnego rodzaju błędów.

Ad fotka: :D
}
2013-02-26 15:46:36 = rafalon
{
Panie Gynvael, wychwyciłem mały błąd (wiem jestem okropny :D), na stronie na powinien być 'Listing 2a' jest 'Listing 3a', mimo że 'Listing 3' jest o stronę dalej :P.
}
2013-02-27 08:35:13 = Programista ABAP
{
Mogliby przedłużyć promocje przynajmniej do 1 Marca jak będę po wypłacie ;-)
}
2013-02-28 11:20:26 = KrzaQ
{
Jestem ciut zaskoczony, bo myślałem, że przed wydaniem artu do druku jest on gruntownie sprawdzany, a listing 1c jest po prostu niepoprawny - nie bierze w ogóle pod uwagę tzw. integer promotion (nie wiem jak to jest po naszemu), przez co wychodzi nam coś takiego: https://ideone.com/8UyKtd

Dodatkowo można zobaczyć generowane instrukcje, gcc wygenerował cmp edx, eax; tutaj: http://tinyurl.com/bt372dq

Jeśli mamy operacje arytmetyczne na typach mniejszych niż int lub unsigned int (a konkretniej na typach o niższej randze), wynik operacji zawsze będzie jednym z powyższych. Dlatego a oraz a+1 w powyższym przykładzie są pierw zamieniane (promowane?) na int, a dopiero potem porównywane. W sumie ten kod by działał na platformie, na której char jest tej samej wielkości co int, o ile są takie.
}
2013-02-28 11:59:24 = Gynvael Coldwind
{
@rafalon
Ups ;)

@Programiasta ABAP
Pytanie czy przy promocji liczy się data złożenia zamówienia czy data wpłaty - zapytaj w wydawnictwie najlepiej :)

@KrzaQ
Well spotted! :)
Faktycznie przykład powinien być przeprowadzony na unsigned int lub if powinien mieć postać:

if(a > (unsigned char)(a + 1)) {

Co do tego co napisałeś, mam jedną uwagę odnośnie:

"Jeśli mamy operacje arytmetyczne na typach mniejszych niż int lub unsigned int (a konkretniej na typach o niższej randze), wynik operacji *zawsze* będzie jednym z powyższych." [zaznaczenie moje]

W standardzie na którym patrzę (C++11, któryś z nowszych draftów) w sekcji Integral promotion jest użyte słowo "can", a nie "will":

"A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int *can* be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue *can* be converted to a prvalue of type unsignedint." [zaznaczenie moje]

Są przypadku, w których konwersja jest pomijana, jeśli nie ma sensu. Np.:
void asdf(unsigned char a) {
int x = a + 1;
unsigned char y = a + 1;
}

g++ 4.6.3 generuje nastepujący "original tree" dla tego (opcja -fdump-tree-original):

;; Function void asdf(unsigned char) (null)
;; enabled by -tree-original


{
int x;
unsigned char y;

int x;
<<cleanup_point <<< Unknown tree: expr_stmt
(void) (x = (int) a + 1) >>>>>;
unsigned char y;
<<cleanup_point <<< Unknown tree: expr_stmt
(void) (y = a + 1) >>>>>;
}

Czyli w pierwszy przypadku konwersja jest, w drugim nie ma.

Anyways, moje artykułu są sprawdzane przez przynajmniej dwie osoby, natomiast cóż, to niestety nie było wychwycone.
Trochę zabawne jest to, że w zasadzie bottom line mojego artykułu - "Programując w językach C lub C++, bardzo łatwo jest popełnić błąd." - został udowodniony bardzo dobitnie :)

Anyways, postaram się wieczorem wrzucić erratę + podziękowania w art :)
}
2013-02-28 12:46:17 = KrzaQ
{
Tutaj nie jestem w 100% pewien, ale tak to widzę:

Kompilator ma prawo wygenerować kod dający ten sam efekt, ale niebędący kalką tego co jest w źródle, tak długo jak efekt jest nie do odróżnienia. Dlatego przy przypisywaniu do mniejszej zmiennej konwersja może być zbędna.

Słówko can faktycznie kłóci się z moją interpretacją, ale jest jeszcze jedna sprawa: "usual arithmetic conversions" (znów nie wiem jak to jest po naszemu), które są (opieram się na n3337) na pewno wykonywane (5.7). Pełna definicja jest w przypisie 9 działu 5 (przed 5.1). Interesujący mnie kawałek to:
— If both operands have the same type, no further conversion is needed.
— Otherwise, if both operands have signed integer types or both have unsigned integer types, the
operand with the type of lesser integer conversion rank shall be converted to the type of the
operand with greater rank.
— Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the
rank of the type of the other operand, the operand with signed integer type shall be converted to
the type of the operand with unsigned integer type.
— Otherwise, if the type of the operand with signed integer type can represent all of the values of
the type of the operand with unsigned integer type, the operand with unsigned integer type shall
be converted to the type of the operand with signed integer type.

Chodzi o ostatni punkt. Mamy unsigned char i int, więc oba są zamieniane na inta.

W sumie fajnie, że sprawdziłem, teraz nie będę mylił tych pojęć :)
}
2014-10-17 15:35:47 = Rafał
{
Czy można połączyć plik exe z plikiem np. pdf lub jpeg tak aby i jedno i drugie zostało uruchomione. Pomijam dodawanie innych plików (np. pdf) do zasobów i odpalanie ich z poziomu aplikacji exe.
}
2014-10-17 22:20:39 = Gynvael Coldwind
{
@Rafał
Nie (wyjątkiem jest exploitacja luki w czytniku jpg/pdf ofc). W sumie nie ma powodu, żeby tak zrobić.

Z ciekawości, czemu pytasz? Szczerze, to takie sztuczki stosuje się chyba tylko i wyłącznie aby podrzucić ludziom backdoor, co brzmi jakby było na granicy prawa (z wyjątkiem jeśli jest na to umowa o pentest, która to dopuszcza ofc, itp).

}

Add a comment:

Nick:
URL (optional):
Math captcha: 7 ∗ 2 + 10 =