C Language
Dependable C
notes:
introduction:
Code should try to avoid relying on the user having the latest version of the compiler. There's also a recommendation to adhering to a subset of C89, which should be able to compile with any version of a compiler.
general advice:
- There are (exotic) platforms that do not guarantee bytes are 8 bits
- Function pointers may not be the same size as data pointers
- Pointers may not be 64 bits
- Types alignment are no more than their size
- Rely on the compiler warnings for the types of bugs you commonly write, and selectively turn on/off or turn into errors as you see fit - Its encourage for header files to be compatible with c++ while discouraging using c++ keywords
concepts of c:
C standard targets an "abstract machine" where it defines what should be the expected "observable" behavior of any code. This allows compilers to optimize code and make changes as it sees fit for every target hardware.
there's a lot of content on UB and why it matters, and that's all good, but I would prefer to have some more specifics as to what are UB, like "reading from uninitialized memory is UB" so I'm taking notes on UB on a separate doc (C).
keywords:
autois not dependable as its meaning has changed between c versions.
types:
Floats and doubles are dependable but there are platforms that may for have a FPU and might emulate in software its behavior or completely miss it. Pretty much all implementations follow the IEEE 754 standard, however different implementations might handle rounding differently.
Use your own implementation of bool since anything not 0 is true. Preferably
int
volatile This helps tell the compiler that a variable might change in ways not apparent to the compiler. ie. another thread modifies the variable. And so, it tells the compiler to avoid optimizing out any potential code paths related to an apparently unmodified variable.
volatile int sth; // value can change
volatile int *sth; // value the pointer points at can change
int *volatile sth; // the pointer itself can change
volatile int *volatile sth; // well... anything can change
operators:
Be careful when == a float. All floats not-a-number are unequal to
themselves, so a isNaN check can look like if (f != f) printf("is Nan")
Null might not reside in the 0x0 address
A shift operator is UB if the left operand is negative or greater-equal than the number of bits in the type
c versions:
c99
_inlinehas been deprecated- Variable Length Arrays are buggy and varying support between versions
c11
- threads.h has varying implementations and support is not reliable. Prefer POSIX threads or any platform native implementation
memory model:
- VLA: Long story short. Don't. (Didn't even knew they existed so... whatever)
- Effective types. Half of it (or more) flew over my head. revisit
dependable UB:
- While all UB should be avoided when possible, there's a couple of things that
have become dependable.
- Pointer to the first member of a struct is the same as the struct
- Replacing standard functions using macros
- Non c standard threading
- Effective typing for functions
The power of 10
notes:
- Simple control flow constructs
- All loops must have a fixed upper-bound
- No dynamic memory allocation after initialization.
- No functions bigger than a single print paper. ~60 lines
- Assertion density as min 2 per function. Side-effect free. Boolean tests
- Data objects should be defined at the smallest level of scope
- The return value of every non-void function must be verified by the caller.
- The use of the preprocessor should be limited to header files and simple macro statements
- The use of pointers should be restricted, to no more than one level of dereferencing.
- Compile with all warnings turned on and most pedantic setting. Rely on static code analyzers.
There seems to be value in some of this rules, but there are others that seem a bit arbitrary. I think, I in particular to my coding style don't really care about 4, 9 and 10. The rest are fine, with the caveat that 6 lack of definition for smallest level of scope allows for globals when needed.
Releasing
Include static files in binaries: https://pragmaticaddict.com/c_static_binary_data.html
Undefined behavior
#todo Sources:
- https://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html @ Part 3
- https://blog.regehr.org/archives/213 @ Part 1 : No Traveling
The C FAQ defines “undefined behavior” like this:
Anything at all can happen; the Standard imposes no requirements. The program may fail to compile, or it may execute incorrectly (either crashing or silently generating incorrect results), or it may fortuitously do exactly what the programmer intended.
Some of these behaviors allow compilers to optimize certain code-paths as they can "assume" stuff.
Some examples:
- Signed integer overflow:
INT_MAX + 1is not guaranteed to beINT_MIN. - Oversized Shift Amounts: Shifting a
u32by 32. - De-referencing a NULL Pointer
- Accessing out of bounds array
- Violating Type Rules: It is undefined behavior to cast an
int*to afloat*and de-reference it (accessing the "int" as if it were a "float").
Buggy code, may execute correctly, but an update on the compiler might bring issues. Compiler ends up been blamed for already incorrect code.
Hot Loading libraries
Linux: Remember to compile library with position independent flag. (in gcc
its -fPIC)
dlopen: Load librarydlsym: Get function pointerdlclosedlerror
Windows: Remember to compile as a dynamic-library. (in cl its /LD)
LoadLibraryAGetProcAddressFreeLibrary
// Function definition
typedef void (*my_function_def)(u8 var_1, char* var_2);
my_function_def my_function = NULL;
int main() {
my_library = dlopen("libfilename", FLAGS);
my_function = dlsym(my_library, "my_function_in_lib");
}
Sources: