Cursed C - snippets from Expert C Programming
Helpful or not, I’m taking some notes while reading the book “Expert C Programming” by Peter van der Linden.
This post is divided into 3 sections
- Tips that could be picked up: good habbits, useful snippets, tips. Takeways that are generally good.
- The black magic: something new, something hacky, I have no idea if I should use them but at least these are cool….
- Cursed but this is the way: some “twisted” side of the language. But you should learn and accept, because they are well-defined.
- Pitfalls / Mistakes: undefined behaviours, bad habbits, common mistakes. Or C’s own problems.
# Tips that could be picked up.
>  
useful tools
(table 6-1 6-2 6-3 6-4)
Tool | Where to find | what it does |
---|---|---|
cflow | AUR | prints the caller/callee relationships of a program |
cscope | extra | interatice c program browser |
ldd | core/glibc | prints dynamic libraries this file needs |
nm | core/binutils | prints symbol table of an object file |
strings | core/binutils | looks at embedded strings |
>  
Literal before variable in comparision
make debugging easier when a equal
sign is missed
|
|
>  
when to (and not to) use unsigned types
Avoid unnecessary complexity by minimizing your use of unsigned types. Don’t use
an unsigned type to represent a quantity just because it will never be negative.
Use a signed type like int and you don’t have to worry about boundary cases in
the detailed rules for promoting mixed types. Only use unsigned types for
bitfields or binary masks. Use casts in expressions to make all the operands
signed or unsigned.
(see Pitfalls/implicit_int_conversion)
>  
when to (and not to) use typedefs
Don’t bother with
typedefs
forstructs
only to save writing the wordstruct
. And you shouldn’t hide the clue.Use typedefs for
– types that combine arrays, structs, pointers or functions.
– portable types’
– casts (to have a simpler name to cast to a complicated type)
>  
length array with variant base type
The former allows the base type of the array to change.
|
|
>  
Minimal visibility of function
Declaring a function as static
storage class makes it only visible within the
file. Do this where applicable. Especially for libraries to declare internal
only functions…
>  
Let caller allocate the buffer, not the callee
|
|
>  
Use UNION to
- save space because only one member could exist at once.
- have different interpretations of the same data.
|
|
>  
STRUCT, UNION and ENUM takes the same form That’s a trivia, but I’ve never noticed it…
struct [optional_tag] {stff...} [optional_variable_definitions];
union [optional_tag] {stff...} [optional_variable_definitions];
enum [optional_tag] {stff...} [optional_variable_definitions];
>  
reset pointer after free()
|
|
# The black magic
>  
setjmp and longjmp
man setjmp
nonlocal gotos: transferring execution from one function to a predetermined
location in another function.
|
|
setjmp()
function saves various information about the calling environment (tycally, the stack pointer, the instruction pointer etc.) in the bufferenv
for later use bylongjmp()
.setjmp()
must be called firstlongjmp()
uses the information saved inenv
to transfer control back to the point where setjmp() was called and to restore (“rewind”) the stack to its state at time of thesetjmp()
call. In addition- Following a successful
longjmp()
, execution continues as ifsetjmp()
had return for a second time. This “fake” return can be distinguished from a truesetjmp()
call because the “fake” return returns the value provided inval
. If programmer mistakenly passes the value 0 inval
the “fake” return will intead return 1. sigsetjmp()
andsiglongjmp()
also perform nonlocal gotos, but provide predictable handling of the process signal mask.
Example:
|
|
the result will be:
first time thtough
in foo()
back in main
A setjmp / longjmp is most useful for error recorvery. If yo udiscover a unrecoverable error, you can transfer control back to the main input loop and start again from there…
setjmp and longjmp have mutated into the more general exception routines “catch” and “throw” in C++
# Cursed but this is the way.
>  
Declaration resembles the use it’s twisted but intended.
|
|
while int *p[3]
(as above) means an array of 3 pointers-to-integers, int (*p) [3]
means an pointer to an array of 3 integers.
Definition | Meaning | Usage | equi. |
---|---|---|---|
int *p[3] | array of 3 pointers-to-integers | *p[3] | int**p |
int (*p)[3] | pointer of an array of 3 integers | (*p)[3] | int (*)[3] |
int (*fun())() | returns pointer to function that return int | int i = (*func())() | |
int (*foo())[] | returns pointer to an array of integers | int i = (*foo())[3] | |
int (*foo[])() | Foo is an array of int function pointers | int i = (*foo[1])() |
Fuck me:
|
|
>  
Combine typedef with declaration: basically the same semantic.
|
|
Fuck me 2:
|
|
>  
typedef v.s. #define
Typedef is a complete “encapsulated” type, you can’t add to it after you have
declared it.
You can extend a macro typename with other type specifiers but not a typedef’d typename
|
|
Typedef’d name provides the type for every declarator in a declaration.
See Pitfalls_and_mistakes/how_not_to_declare_multiple_pointers
>  
Qualified types and pointer assignments
in C there are 4 type qualifiers 1
const (C89) : a value will not be changed. Results of attampt to change is
implementation-defined
volatile (C89) : objects are omitted from optimization because the value can be
changed from outside of the current scope at any time.
restrict (C99) : (when used with a pointer), it tells the compiler that ptr is
the only way to access the object pointed by it. Vialation is
UD - this is C only.
_Atomic (C11) : to avoid race condition..
in ANSI C: both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed by the right.
|
|
>  
pointer is NOT array
It’s said “pointer is identical to array” because array is implicitly converted to pointer as a r-value (or function parameter). The conversion is done by taking the address of the first element.
|
|
The example is from here
But pointer and array are DIFFERENT THINGS:
int * ptr // ptr is a variable that holds the address of an int
int arr[10] // arr is a sequence of 10 ints in memory
with extern
it goes funky: (assuming both long and pointer are 64bits)
|
|
The problem is: arr is declared to be a pointer. Therefore arr[2]
means
*(arr+2)
, however arr
in file1 is defined to be an array, the value of arr
is 1 (cut off the first 64 bits of the array sequence).
Which means, arr[2]
finally becomes *(1+2)
, that’s dereference is garbage.
>  
Precedence… rules?
[TODO]
▸▸ Associativity, what is a=b=c
?
All assignment-operator have right associativity, the right most operation in
the expression is evaluated first, and evaluation proceeds from right to left.
|
|
>  
Multiple function calls in an expression, which first?
|
|
while the evaluation groups g() and h() for multiplication then f() for addition, you can’t assume which function is called first.. So if these functions have side effect that influences each other, don’t mix them in an expression!
>  
Maximal munch
|
|
>  
You can return a pointer to string literal
|
|
>  
ANSI and K&R Function Prototypes and Declarations, DO NOT MIX
|
|
Either style is supported, but do not mix the usage: if declared with K&R then define in K&R, vice versa!
- Under K&R, if youp assed anything shorter than an int to a function it actually got an int; and floats were expanded to doubles. The values are automatically trimmed back to the corresponding narrower types in the body of the called function.
- Under ANSI the parameters are passed “as is” specified in the prototype – the default argument promotions do not occur.
|
|
# Pitfalls / Mistakes
>  
const doesn’t make constant
const
qualifier makes the value read-only through that symbol; it doesn’t not
prevent the value from being modfied through other means. const
is mostly used
for qualifying pointer parameter, to indicate that this funtion will not change
the data that argument points to.
The combination of const and * is usually only used to simulate call-by-value for array parameters. It says, “I am giving you a pointer to this thing, but you may not change it.
>  
implicit int type conversion
|
|
the result reads: “-1 is bigger than 20”. Because while comparing a unsigned
int to signed int, the LHS value is promoted to unsigned and (unsigned int)-1 == 0xffffffff
is a LARGE one!
>  
sizeof doesn’t need () but don’t abuse it…
|
|
like, what the hack should this be? And this?
|
|
Trivia: it depends on the type of p. If a pointer, the later is error, if a number, the first is an error.
>  
typedef is pretty free of form but don’t abuse
|
|
>  
how not to declare multiple pointers
|
|
# Terms from ANSI C
- Implementation-defined : compiler-writer chooses what happens and how to document it.
- unspecified : for something correct, on which the standard does not impose any requirements.
- undefined : for something incorrect, on which the standard does not impose any requirements. Anything is allowed to happen.
- a constraint : a restriction or requirement that must be obeyed.
[COPYRIGHT & DISCLAIMER]
This article is licensed under CC BY-NC-SA 4.0
All contents in this post, unless stated otherwise, are directly or indirectly taken from the book “Expert C Programming”2. ALL copyrights reserved by the author.-
https://en.wikipedia.org/wiki/Type_qualifier and https://www.geeksforgeeks.org/ ↩︎
-
Expert C Programming - Deep C Secrets, Peter van der Linden. The book is publicly available under https://progforperf.github.io/references.html ↩︎
[+] click to leave a comment [+]
>> SEND COMMENT <<