"C is just C++ without classes"
ANSI C has not been a subset of C++ since 1999!
- Compound literals
- Designated initializers
- Flexible array members
- Variable length arrays
- It's easier to have syntactic smells in C++ than in C (mostly because of template horrors)
- Casts are also less complex in C because it always reinterprets and leaves full control to the programmer (but with great power comes great responsibility)
- Types are tailored for a specific type instead of being generic (this has also extremely big inconvenients because you have to reinvent the wheel every time)
- If you do not cast or restrict your use of void pointer conversions, you get a solid safety assertion enforced by the type system
- Opaque types help to provide a data type without unsafely exposing its internals
- Better varargs
- keyword arguments
- Macro pseudo metaprogramming
- All the examples below
- GNU extensions built mostly for the linux kernel -> Stronger developer community for C on *nix
- malloc/use/free cycle for long term storage
- nontrivial to track for complex programs
- many prefer automatic storage and passing pointers as arguments -> but stack size is limited
Is there a way to reproduce the behaviour of automatic storage on the heap ?
In C++ we have native RAII and smart pointers
- Smart pointers denote ownership of the dynamic memory, with
unique_ptr
having only one owner andshared_ptr
having multiple ones.- Memory allocated with
unique_ptr
is freed when the owning smart pointer is destroyed; and memory allocated withshared_ptr
is freed when all the owning smart pointers are destroyed.
- Memory allocated with
- RAII is simply cleaning up your used ressources in an object's destructor.
- Known examples are standard streams
In C however, we are pretty limited, but there are workarounds.
- MSVC:
__try
/__finally
- Exception safe on windows
- Simple but verbose, you still need to manually call the cleanup function
- GCC:
__attribute__((cleanup(fun)))
- Variable attribute
- Calls fun with the pointer to the variabled being cleaned up when exiting the scope
- Bypassed by
noreturn
functions (exit
,longjmp
) and signals
Both are nonstandard C, however.
- Introducing
autofree
: the keyword that automatically frees your dynamically allocated memory - Same concept can be applied for an
autoclose
andautofclose
keyword.
Problem: it's still not good enough
- Nested pointers in structure are untouched
- Variables cannot live outside the scope
We introduce smalloc
and sfree
to wrap malloc
and free
and implement
our special logic
- We attach metadata by reserving some space before the allocated memory zone
- We get destructors!
sfree
becomes the universal deallocator, akin todelete
in C++- let's make a
smart
keyword to automatically callsfree
when going out of scope
What we get is a simple working implementation of unique_ptr
- We need to be able to share a pointer between multiple owners, hence we add a counter to the prepended metadata.
sfree
decrements the counter, and we add asref
function that increments it; if the counter goes to 0,free
is called on the memory block.- however this is not thread safe; and adding a lock would slow allocation down horribly
- We get to (manually!) implement atomic operations over size_t (because C11 is still not that widespread)
- We also get to enjoy race conditions if we don't carefully think the code through
- To add even more complexity we need to implement weak pointers that require another independent counter for weak pointer instances
- And everything needs to be thread safe
After countless hours of pain we get a nice shared_ptr
implementation.
smalloc
takes a lot of parameters by now, so we wrap it up in a macro- we introduce the
unique_ptr
andshared_ptr
macros that take a type instead of a size - we also introduce smart arrays under the same interface (this one was hard)
Doing all this and we get an extremely satisfying result.
- Current (known) unit testing frameworks for C are a mess
- They are stable but require lots of boilerplate code
- You need to care about the implementation before adding tests
- All other languages have an automatic test registration mechanism
- All other languages doesn't force you to set up your test runner
Framework hall of shame:
- CUnit is horribly verbose and you get to manually clean up his garbage (CU_cleanup_registry() extravaganza)
- Check, WTF ARE YOU DOING WITH MACROS AND M4 (BAD CHECK, BAD)
What we actually need:
- Default entry point that setups the test runner with sane defaults and provides useful command line parameters
- Automatic test registration
- Test & Suite metadata set with a declarative syntax
In order to stand a chance in front of existing solution, we need to being feature-complete
- xUnit structure (tests, suites, fixtures, runner)
- TAP 12 support
- Custom logging & reporting
- Command-line arguments & environment variables to alter runtime behaviour
- Test filtering and matching (globbing for simplicity)
- Be crash-resilient; DO NOT TRUST USER CODE
- Also test crashes (signals) where they are expected
But also:
- Being simple
- Being extensible
- Being solid (smart pointers help wink)
- Static and shared libraries can provide a default main that can be overriden by user code
- We implement a main function that uses sane defaults, command line parameters and environment variables
- We provide a hook mechanism to allow the user to handle events from the runner without needing to provide a main
- GCC/Clang/MSVC allows you to put data in arbitrary sections
- We use this to associate metadata to tests, suites, and report hooks -> beware of padding between variables in a section
- We iterate the structures when the runner is started and set up everything
- Show sample to test malloc()
- Parallel test execution
- Concolic test generation for functions
- Windows support
Programs are made by humans However humans are flawed Therefore these programs are bugged
Hence a program cannot properly function without a debugger, which in turn cannot properly function without debugging information
- Statements claiming that runtime reflection cannot be done are utter bullshit
- Debuggers use runtime remote introspection all the time
- typing
var->x
in your debugging console proves that it knows thatx
exists invar
and what its offset is.
- DWARF and libdwarf++
- Traversing the DIE tree
- Building incremental metadata
- Get TypeInfo from type, expression
- Manipulate an object from its TypeInfo
- Mutate its internals
- Call any method
- Instanciate an object
- Having fun with the standard library
- Universal marshalling!
- Object views and mirrors!
- Wrappers and proxies!
- Object instanciation from type info!