Limits
https://en.cppreference.com/w/c/types/limits
| Type | Min value | Max value | 
|---|---|---|
| char | CHAR_MIN | CHAR_MAX | 
| signed char | SCHAR_MIN | SCHAR_MAX | 
| unsigned char | UCHAR_MIN | UCHAR_MAX | 
| short | SHRT_MIN | SHRT_MAX | 
| unsigned short | USHRT_MIN | USHRT_MAX | 
| int | INT_MIN | INT_MAX | 
| unsigned int | UINT_MIN | UINT_MAX | 
| long | LONG_MIN | LONG_MAX | 
| unsigned long | ULONG_MIN | ULONG_MAX | 
| long long | LLONG_MIN | LLONG_MAX | 
| unsigned long long | ULLONG_MIN | ULLONG_MAX | 
scanf
Use of scanf() is usually frowned upon as it doesn't give the programmer enough control over what could happen (Eg: buffer overflow attack).
Yet, it's quite useful to get stuff done quick when you are just messing around with C while learning it.
In that sense, scanf() is in fact, quite useful.
Format specifiers
| FS | Type | 
|---|---|
| %zu | size_t | 
| %n | Number of chars written so far | 
| %o | Octal | 
| %% | Literal '%' | 
| %c | char | 
| %d | int | 
| %s | string | 
| %f | int | 
| %n | a special one | 
| %d | int | 
| %u | unsigned int | 
| %p | address (pointer) | 
| %x | int as hexadecimal (0x, 0X, ,) | 
| %X | int as hexadecimal (0x, 0X, ,) | 
| %[^\n] | Exclude (till \n) | 
Modifiers:
- l: long
- ll: long long
| Format specifier | Type | 
|---|---|
| %ld | long int | 
| %lld | long long int | 
| %lf | double | 
| %lu | long unsigned | 
See: https://en.cppreference.com/w/c/io/fprintf
- Precision
- %.2f
- %.f // No precision. Truncate.
 
- Width and alignment
- Right alignment: %2f
- Left alignment: %-2f
 
- *- In printf(), variable as width: `%*.*f`, width, precision
- In scanf(), suppresses assignment: `%*f`
 
- Wide characters
- L' stuff as format specifiers.
 
Interesting behavior and facts
Undefined behaviour means that compiler can do whatever it likes.
No evaluation within sizeof
#include<stdio.h>
int main() {
    int a=3, b;
    b = sizeof(a=10);
    printf("a=%d, b=%d\n", a, b);
    // a=3, b=4
    // Value of b is compiler/arch dependent though.
    return 0;
}(Thanks to Kevin for telling me this.)
—
Expression inside sizeof is not evaluated. So the value of a never changed.
In sizeof(a=10), type of a=10 is looked at. It is found to be int. So essentially, it is same as saying sizeof(int) or sizeof(10), I guess.
sizeof(int) is usually 4, but can be different depending on architecture and compiler.
Multiple sequence points
Multiple sequence points => undefined behaviour.
#include<stdio.h>
int main() {
    int a=3;
    printf("%d\n", a++ + ++a);
    // 8
    // Needn't be so though. It's undefined behaviour.
    return 0; 
}Evaluation could be like:
- ((a++) + ++a))
- (a++ + (++a))
(Thanks to Kevin for reminding me about this.)
—
Similarly, the following are also invoke undefined behaviour due to multiple sequence points.
- i = i++
- i = ++i
- printf("%d %d", i++, i++)
See:
- https://stackoverflow.com/questions/26894509/c-why-does-i-i-invoke-undefined-behaviour
- https://stackoverflow.com/questions/4968854/is-the-behaviour-of-i-i-really-undefined
- https://stackoverflow.com/questions/22616986/order-of-evaluation-of-arguments-in-function-calling
Order of function arg eval
Order of function argument evaluation is not defined in the C standard.
The following snippet could print "HiHello" or "HelloHi".
f(printf("Hi"), printf("Hello"));Calling main from main
Looks like this is possible in C (gcc).
#include<stdio.h>
int ctr = 5;
int main() {
    if(ctr>0) {
        printf("Hi\n");
        ctr--;
        main();
    }
}
/*
Hi
Hi
Hi
Hi
Hi
*/Apparently, C++ standard prohibits this though gcc allows it.
Digraphs
| Digraph | Equivalent | 
|---|---|
| <: | [ | 
| :> | ] | 
| <% | { | 
| %> | } | 
| %: | # | 
| %:%: | ## | 
Trigraphs
- Used when old keyboards didn't have some of the keys common today.
- Silently changes the tokens, so considered unsavory.
| Trigarph | Result | 
|---|---|
| ??= | # | 
| ??( | [ | 
| ??/ | \ | 
| ??) | ] | 
| ??' | ^ | 
| ??< | { | 
| ??! | │ | 
| ??> | } | 
| ??- | ~ | 
C pre-processor (CPP)
- Define a macro: #define NAME body
- Undefine a macro: #undef NAME
- Check if a macro is defined: #ifdef
- Check if a macro is not defined: #ifndef
- Conditions: #if,#elif,#else,#endif
- Token concatenation of aandb:a##b
- Stringification of a:#a
// Check if a macro is defined
#ifdef NAME
  print("NAME is defined\n");
#else
  print("NAME is not defined\n");
#endifOften ignored error checking
- Return value of malloc, printf, scanf
- strcat, sprintnf, strcpy, etc
- strtoint
More
- Bit field packing
- Div by zero gives INF for float. Segfault for int
- Original C compiler was 1-pass. That's why it mandated separate function declaration and definition
- malloccan return pointer to a smaller memory area even if the requested amount of memory isn't available.- Overcommit. Kernel overcommits by default 
- sysctl vm.overcommitmemory - $ sudo sysctl vm.overcommit_memory vm.overcommit_memory = 0
 
- Heap is part of process' address space, which is part of virtual memory
- Virtual memory could be thought of RAM + Swap (not the same though??)