Czasem będę używał skrótu UB co oznacza Undefined Behavior. W bardzo wielkim skrócie to oznacza że w standardzie języka zauważono stosowanie takich konstrukcji ale świadomie nie definiuje się zachowania kompilatora ze względu na wpływ na wydajność kodu tworzonego przez tenże kompilator. Tworząc oprogramowanie masz po prostu wiedzieć że... tak się nie robi. Stosując UB, ryzykujesz że organizacja znana z historii pod tym akronimem wpadnie oknem, wyrwie Ci głowę i zawoła do pustego korpusu „czy wiesz co robisz?" :-/
Tak, wiem że to makabryczny żart ale naprawdę może zdarzyć się wszystko. Włącznie z usunięciem danych z dysku.
To przeczytasz co oznacza UB.
https://en.wikipedia.org/wiki/Undefined_behavior
Jeśli jakieś zagadnienie spotka się z powszechnym zaciekawieniem, pokażę jak można użyć sensownie daną technikę. A jeśli nie.. cóż.. przynajmniej nie zaginie
Punkty sekwencyjne jako element działania języka oraz częste źródło UB
Język C uzgadnia stan zmiennych (czyli inaczej tzw. efekty uboczne) wyłącznie w określonych punktach. Robi to ze względu na wolność pozostawioną twórcom kompilatora. Umożliwia to implementację kompilatora wydajnie na danej platformie.
https://en.wikipedia.org/wiki/Sequence_point
Kod: Zaznacz cały
int i = 0;
int f(int x) {
return x + i;
}
int x = f(i++);
int y = (i++) + i;
y jest niezdefiniowane bo zależy od efektów ubocznych (inkrementacji) która w momencie wywołania nie jest znana. x jest zdefiniowane bo nie zależy od tych efektów.
setjmp() i longimp() jako implementacja nielokalnego skoku oraz możliwość implementacji współprogramów, wyjątków i kontynuacji
To naprawdę potężny mechanizm wymagający oddzielnego omówienia.
http://en.m.wikipedia.org/wiki/Setjmp.h
https://en.wikipedia.org/wiki/Continuation
Użycie specyfikatora %n w printf()
Kod: Zaznacz cały
#include <stdio.h>
int main() {
int count;
printf("Witamy%n na forum Microgeek!\n", &count);
printf("Słowo \"Witamy\" zawiera %d znaków.\n", count);
}
Przy prawidłowym działaniu zwraca:
Kod: Zaznacz cały
Witamy na forum Microgeek!
Słowo "Witamy" zawiera 6 znaków.
Inne zastosowanie, liczenie ilości znaków wprowadzonych:
Kod: Zaznacz cały
sscanf(string, "%d%n", &number, &length );
Czasem warto zastosować dla nieznanej ilości znaków w ciągu:
Kod: Zaznacz cały
int pos1, pos2;
char *string_of_unknown_length = ”nie mam zielonego pojęcia ile ten napis ma znaków";
printf(”Oto napis: %n(%s)%n.\n”, &pos1, string_of_unknown_length, &pos2);
printf("%*s", pos1+1, " ");
for(int i=pos1+1; i<pos2-1; i++) {
putc('-', stdout);
}
putc('\n', stdout);
W wyniku ładnie podkreślony napis.
Funkcje biblioteki standardowej które są mniej znane
qsort(), bsearch(), strpbrk(), strcspn(). Ostatnie dwie mogą zastąpić strtok().
Zapoznaj się i napisz przykład a zobaczysz że się przyda.
Czytanie skomplikowanych typów od prawej do lewej
http://edu.pjwstk.edu.pl/wyklady/pro/scb/PRG2CPP_files/node33.html
http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html
Kod: Zaznacz cały
int a[5]; /* a[0], ..., a[4] to tablica 5 elementów typu int */
int (*pa)[5]; /* (*pa)[0], ..., (*pa)[4] to wskaźnik na tablicę 5 elementów typu int */
int *ap[5]; /* *ap[0], .., *ap[4] to tablica 5 elementów typu wskaźnik na typ int */
int *p; /* *p to wskaźnik na typ int */
Jawna inicjalizacja (C99)
Kod: Zaznacz cały
struct Foo {
int x;
int y;
int z;
};
struct Foo foo = {.z = 3, .x = 5};
Ta sama składnia obowiązuje także dla tablic. Jeśli nie wymienisz jawnie indeksu, element pod tymże zostanie wyzerowany.
Kod: Zaznacz cały
int a[5] = {[1] = 2, [4] = 5};
int a[] = {[1] = 2, [4] = 5};
int a[5] = {0, 2, 0, 0, 5};
W połączeniu można tego użyć tak:
Kod: Zaznacz cały
#include <stdio.h>
typedef struct {
int a;
double b;
} s_t;
int main(void) {
s_t values[5] = {
[0] = { .a = 1, .b = 13.2 },
[1] = { 10, 10.0 },
[3] = { .a = 4, .b = 45.1 }
};
printf("%d %f\n", values[1].a, values[1].b);
}
Wskaźniki z restricted (C99)
Słowo kluczowe restricted może być zastosowane dla wskaźnika przekazywanego do funkcji. Oznacza ono że dostęp do wartości na który wskazuje wskaźnik, będzie odbywał się wyłącznie z użyciem tegoż wskaźnika. Jeśli na wartość wskazuje także inny wskaźnik, standard C99 mówi o UB.
Kod: Zaznacz cały
int f(const int* restrict x, int* y) {
(*y)++;
int z = *x;
(*y)--;
return z;
}
.. informujemy kompilator że zmienne x i y nigdy nie będą wskazywały na tę samą przestrzeń danych. Choć to naiwny przykład to kompilator zoptymalizować może to do:
Kod: Zaznacz cały
int f(const int* restrict x, int* y) {
return *x;
}
Jeszcze raz, jeśli x i y jako wskaźniki wskazują na ten sam obszar pamięci, mamy do czynienia z UB!
Kombinacja działania const i volatile
http://embeddedgurus.com/barr-code/2012/01/combining-cs-volatile-and-const-keywords/
Statyczne i jawne parametry tablicy (C99)
Kod: Zaznacz cały
void f(int a[static 10]) {
/* ... */
}
Jest obietnicą że przesłana tablica zawierać będzie co najmniej 10 elementów. Jeśli będzie mniej lub null i będzie to możliwe do określenia na etapie kompilacji, kompilator zgłosi ostrzeżenie. Dodatkowo kompilator może poczynić optymalizacje że tablica nigdy nie będzie pusta (is not null).
Kod: Zaznacz cały
void f(int a[const]) {
/* ... */
}
.. obiecujemy nie modyfikować wskaźnika a. To taki sam efekt jak: f(int * const a) {... }.
W połączeniu ze static, uzyskujemy jednak coś czego nie da się inaczej jawnie zapisać:
Kod: Zaznacz cały
f(int a[static const 10]){ ... }
Nie dość że dane są stałe, to jeszcze nie będzie ich mniej niż 10.
Taki sam zapis obowiązuje także dla restrict i kombinacji z poprzednimi dwoma.
Wyrażenia generyczne (C11)
Umożliwiają wybór definiowanego elementu w zależności od typu przekazanych danych. Najczęściej występuje w makrach choć działa także poza nimi. Nie znaczy to oczywiście że C umożliwia przeciążanie funkcji.
od C11 umożliwia jedynie wybór co jest definiowane.
Kod: Zaznacz cały
#define cbrt(X) _Generic((X), \
long double: cbrtl, \
default: cbrt, \
float: cbrtf \
)(X)
Wywołanie cbrt(expr) jest przekładane na cbrtl(expr) jeśli wyrażenie expr jest typu long double, cbrtf jeśli jest typu float i cbrt w innych przypadkach.
Sposób na "stringifikację" komend
Jeśli mamy kod który łączy polecenie lub komendę z funkcją lub wskaźnikiem funkcji do wykonania, np taki:
Kod: Zaznacz cały
struct command {
char *name;
void (*function_com) (void);
};
struct command commands[] = {
{ "show", show_command },
{ "exit", exit_command },
...
};
.. i widać regularność w nazewnictwie funkcji (tu: nazwa_command), można posłużyć się makrem łączącym jedno z drugim:
Kod: Zaznacz cały
#define COMMAND(NAME) { #NAME, NAME ## _command }
struct command commands[] = {
COMMAND (show),
COMMAND (exit),
...
};
Nigdy nie było mi to potrzebne ale jak chcesz to sprawdź
Swoją drogą.. gdzie ja to znalazłem?
Kod: Zaznacz cały
#include <stdio.h>
#include <math.h>
int main() {
printf("%.0f\n",pow(2,747));
return 0;
}
Zwraca poprawną wartość:
Kod: Zaznacz cały
740298315191606967520227188330889966610377319868419938630605715764070011466206019559325413145373572325939050053182159998975553533608824916574615132828322000124194610605645134711392062011527273571616649243219599128195212771328
Preprocesor robi co do niego należy czyli … włącza
Kod: Zaznacz cały
#include <stdio.h>
void main()
{
#include "next_program.c"
}
.. a w pliku next_program.c jest np. to:
Kod: Zaznacz cały
printf("Witam na forum Microgeek.eu!\n");
Często wykorzystuję tę technikę do włączania tablic wyliczonych w trakcie budowania oprogramowania.
Przekazywanie danych pomiędzy funkcjami
Kod: Zaznacz cały
#include <stdio.h>
void init_message(void) {
char msg[250] = "Witam Państwa bardzo serdecznie w następnym odcinku"
"serialu o dziwnych właściwościach języka C";
}
void show_message(void) {
char msg[250];
printf("%s\n", msg);
}
int main(void) {
init_message();
show_message();
}
No tak także można ale chyba każdy widzi jaki program staje się przez to "kruchy"
Dodawanie dwóch liczb bez operatora dodawania
Funkcja printf(), zwraca ilość wyświetlanych znaków. Ten efekt można wykorzystać do sumowania znaków. Po co i dlaczego.. bo można
Kod: Zaznacz cały
#include <stdio.h>
int add(int a,int b){
if(a!=0&&b!=0)
return printf("%*c%*c",a,'\r',b,'\r');
else return a!=0?a:b;
}
int main(){
int A = 0, B = 0;
printf("Enter the two numbers to add\n");
scanf("%d %d",&A,&B);
printf("Required sum is %d",add(A,B));
}
Ten sam efekt można osiągnąć z użyciem operatorów bitowych i rekurencji:
Kod: Zaznacz cały
int add(int x, int y)
{
if (y == 0) {
return x;
} else {
return add( x ^ y, (x & y) << 1);
}
}
Nietypowe użycie operatora warunkowego
Obecnie działa w C++. W C (clang, gcc) nie..
Tradycyjne użycie:
Kod: Zaznacz cały
x = (y < 0) ? 10 : 20;
.. a teraz ciekawostka:
Kod: Zaznacz cały
(y < 0 ? x : y) = 20;
Można i tak:
Kod: Zaznacz cały
(b ? trueCount : falseCount)++
Czy można zwrócić void?
Ano można..
Kod: Zaznacz cały
static void foo (void) { }
static void bar (void) {
return foo(); // Tu zwracany jest typ void
}
int main (void) {
bar();
}
Operator przecinka nie taki straszny
Przypominam: Operator przecinka wykonuje operacje od lewej do prawej ale sam zwraca efekt działania operacji najbardziej skrajnej prawej. Z racji słabszego wiązania niż =, stosowany często w nawiasach ().
Kilka propozycji użycia:
Kod: Zaznacz cały
for (int i=0; i<7; ++i, exec_fun())
{
/* kod.. */
}
...
int z = (printf("Przypisanie do z\n"), get_val());
...
Operatory logiczne && (and) i || (or), wykonują operacje „leniwie"
Jak wiadomo jeśli 1 argument && nie jest prawdą, drugi nie jest już sprawdzany. Także jeśli pierwszy argument || jest prawdą, nie ma sensu sprawdzać drugi.
Fakt ten można wykorzystać do wykonywania kodu warunkowego:
Kod: Zaznacz cały
#include <stdio.h>
#include <stdbool.h>
bool true_show(void) {
printf("true\n");
return true;
}
bool false_show(void) {
printf("false\n");
return false;
}
int main(void) {
printf("true_show() && false_show()\n");
true_show() && false_show();
printf("true_show() || false_show()\n");
true_show() || false_show();
printf("false_show() && true_show()\n");
false_show() && true_show();
printf("false_show() || true_show()\n");
false_show() || true_show();
}
Po połączeniu z operatorem przecinka, daje to możliwość innego zapisania konstrukcji if(...) else if(...) else:
Kod: Zaznacz cały
#include <stdio.h>
#include <stdbool.h>
void old(void) {
printf("Gratulujemy słusznego wieku!\n");
}
void adult(void) {
printf("No to jesteś dorosły!\n");
}
void child(void) {
printf("W świetle prawa jesteś dzieckiem!\n");
}
int main(void) {
unsigned age;
printf("Podaj wiek: ");
scanf("%d", &age);
/*
* Odpowiednik:
* if(age < 18) {
* child();
* } else if(age < 80) {
* adult();
* } else if(age >= 80) {
* old();
* }
*/
(void) (((age < 18) && (child(), true))
|| ((age < 80) && (adult(), true))
|| (age >= 80 && (old(), true)));
}
Inny przykład:
Kod: Zaznacz cały
#include <stdio.h>
int main()
{
1 || puts("Siema\n");
0 || puts("Co tam\n");
1 && puts("Słychać\n");
0 && puts("Misiu\n");
}
Niebezpieczeństwo stosowania ciągów formatujących
https://crypto.stanford.edu/cs155/paper ... ng-1.2.pdf
Szybkie zerowanie struktur
Kod: Zaznacz cały
struct mystruct a = { 0 };
Podobnie można także zerować tablice.
Wartość int może mieć przypisany napis
Kod: Zaznacz cały
int a = 'abcd';
W ten sposób można wykryć big/little endian.
Dla 32-bit x86 wynik to:
Kod: Zaznacz cały
0x61626364
Dzięki tej właściwości, enum może mieć lepszy opis:
Kod: Zaznacz cały
enum state {
stopped = 'STOP',
running = 'RUN!',
waiting = 'WAIT',
};
Oczywiście ta technika nie jest przenośna na inne platformy gdzie int może mieć min 16 bitów.
Zamiana zmiennych z użyciem xor
Obecnie nie zaleca się tego sposobu. Lepiej użyć zmiennej pomocniczej. No ale znać warto bo to ulubiony sposób sprawdzania przez HR czy znasz C
Kod: Zaznacz cały
a ^= b ^= a ^= b;
Duff's device
case/switch działa przekraczając blok kodu:
https://en.wikipedia.org/wiki/Duff%27s_device
Kod: Zaznacz cały
void my_strncpy(to, from, count) {
char *to, *from;
int count;
{
int n = (count + 7) / 8;
switch (count % 8) {
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
}
}
Implementacja współprogramów dzięki temu chwytowi. Ten pan jest twórcą putty
http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
Indeksowanie w tablicy
Tradycyjnie użyte table[x] i x[table], są sobie równoważnie bo: v[index] == *(v + index) oraz index[v] == *(index + v)
Kod: Zaznacz cały
#include <stdio.h>
#include <stdbool.h>
int main(void) {
char * msg[] = {"false", "true"};
int values[] = {0, 10, 20, 30, 40, 50, 60};
bool value = true;
unsigned index;
printf("value = %s\n", msg[value]);
printf("value = %s\n", value[msg]);
// Oraz...
printf("Podaj wartość od 0 do 5: ");
scanf("%d", &index);
printf("Wartość: %d * 10 = %d\n", index, index[values]);
}
Inne użycie:
Kod: Zaznacz cały
hex = "0123456789abcdef"[someNybble];
hex = someNybble["0123456789abcdef"];
Compound literals czyli złożone literały
http://en.cppreference.com/w/c/language/compound_literal
http://www.drdobbs.com/the-new-c-compound-literals/184401404
Kod: Zaznacz cały
#include <stdio.h>
void show_values_term_zero(int table[]) {
size_t counter = 0;
while(table[counter] != 0) {
printf("%d\n", table[counter]);
++counter;
}
}
int main(void) {
show_values_term_zero((int[]){1,3, 5, 6,0});
}
Asercja statyczna
W trakcie kompilacji, enum jest przetwarzany:
Kod: Zaznacz cały
// Upewnienie się że VAL jest potęgą 2'ki i mniejsze niż MAX_VAL
#define VAL 4
#define MAX_VAL 64
enum CompileTimeCheck
{
VAL_POW_2 = 1/(((VAL) & ((VAL) - 1)) == 0),
VAL_LESS_64 = 1/((VAL) < (MAX_VAL))
};
int main(void) {
;
}
Pomysł polega na sprowokowaniu dzielenia przez 0.
Nie można tworzyć tablicy o wielkości ujemnej co można użyć w ten sposób:
Kod: Zaznacz cały
#ifdef __GNUC__
#define STATIC_ASSERT_HELPER(expr, msg) \
(!!sizeof \ (struct { unsigned int STATIC_ASSERTION__##msg: (expr) ? 1 : -1; }))
#define STATIC_ASSERT(expr, msg) \
extern int (*assert_function__(void)) [STATIC_ASSERT_HELPER(expr, msg)]
#else
#define STATIC_ASSERT(expr, msg) \
extern char STATIC_ASSERTION__##msg[1]; \
extern char STATIC_ASSERTION__##msg[(expr)?1:2]
#endif /* __GNUC__ */
Makro upraszczające debugowanie dzięki zastosowaniu zmiennej liczby argumentów
Kod: Zaznacz cały
#define ERR(name, fmt, ...) fprintf(stderr, "ERROR " #name ": " fmt "\n", \
__VA_ARGS__)
Użycie...
Kod: Zaznacz cały
ERR(errCantOpen, "File %s cannot be opened", filename);
Więcej na temat poprawnego zdefiniowania tego narzędzia tu:
https://en.wikipedia.org/wiki/Variadic_macro
Dzięki VLA, można łatwiej obsługiwać struktury pakietów (C99)
Kod: Zaznacz cały
void process(uint32_t payload_size) {
uint8_t buffer[sizeof(Protocol_t) + payload_size];
....
}
Efekt podobny do alloca() ale nieco lepiej opisuje intencje.
Wyczyszczenie bufora wejściowego
Kod: Zaznacz cały
scanf("%*[^\n]%*c");
Lambda w GCC
Dzięki temu że w GCC można definiować funkcje wewnątrz innych funkcji, z użyciem małego makra można zdefiniować lambdy:
Kod: Zaznacz cały
#define lambda(return_type, function_body) \
({ return_type fn function_body fn })
Użycie:
Kod: Zaznacz cały
lambda (int, (int x, int y) { return x > y; })(1, 2)
Rozwija się do:
Kod: Zaznacz cały
({ int fn (int x, int y) { return x > y } fn; })(1, 2)
Funkcja printf() umożliwia formatowanie zależne od zmiennej
Kod: Zaznacz cały
#include <stdio.h>
#include <math.h>
int main(void) {
int prec;
printf("Do ilu wartości po przecinku wyświetlić PI?: ");
scanf("%d", &prec);
printf("%.*f\n", prec, M_PI);
}
Znacznik w ciągu formatującym to gwiazdka.
Przekazywanie funkcji jako argument
Kod: Zaznacz cały
#include <stdio.h>
typedef void (*function_t)(int);
void fun1(int a) {
printf("Jestem fun1(%d).\n", a);
}
void fun2(int a) {
printf("Jestem fun2(%d).\n", a);
}
void executor1(function_t function, int a) {
(*function)(a);
}
function_t fun_chooser(int i) {
if(i == 0) {
return fun1;
}
return fun2;
}
int main(void) {
executor1(fun1, 42);
fun_chooser(1)(88);
}
Wynik:
Kod: Zaznacz cały
Jestem fun1(42).
Jestem fun2(88).
Funkcje ze zmienną ilością argumentów (tzw. wariadyczne)
http://rosettacode.org/wiki/Variadic_function#C
X-Makro
https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros
[url]Wewnętrzne makra kompilatora[/url]
Nagłówek intrin.h lub intrinsics.h, zawiera wewnętrzne funkcje i makra kompilatora.
GCC: https://gcc.gnu.org/onlinedocs/gcc/Target-Builtins.html#Target-Builtins
Clang: http://clang.llvm.org/docs/LanguageExtensions.html
Np. dla AVR będzie to <avr/buildins.h>
Oznaczenie warunku jako często występującego lub nie
Kod: Zaznacz cały
Technika dla gcc na platformie x86.
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)
void foo(int arg)
{
if (unlikely(arg == 0)) {
do_this();
return;
}
do_that();
...
}
Używana w jądrze GNU/Linux.
Blokowanie ostrzeżenia o nieużywanej zmiennej
http://stackoverflow.com/questions/4030959/will-a-variablename-c-statement-be-a-no-op-at-all-times/4030983#4030983
Switch/case z użyciem operatora ?
Kod: Zaznacz cały
string result =
a==0 ? "zero" :
a==1 ? "one" :
a==2 ? "two" :
0;
Wygenerowanie listy makr wbudowanych
Kod: Zaznacz cały
touch emoty.c
gcc -E -dM empty.c | sort >gcc-macros.txt
clang -E -dM empty.c | sort > clang-macros.txt
https://msdn.microsoft.com/en-us/library/b0084kay(v=vs.140).aspx