2017-10-02

c programming

the uncommon way

single compilation unit

include source files into a single main file

benefits: only one source file has to be compiled, much simpler and easier to manage, eventual performance benefits

downside: included files might share scope (static and extern modifiers)

the traditional way is to compile source files separately, maintain header files for each, and then link the resulting object files together. this has the benefit that repeated compilation can be quicker in the case that not all objects have to be recompiled; it has the downside that the general complexity is higher because of having to manage header files, multiple object files, linking with eventual performance disadvantages and complex makefiles

further information: wikipedia article

headers can still be used to declare the interface of a shared library

return status and error handling

use a status object with status code and source library id

use one goto label for clean-up and return

benefits: deduplicated error checking and clean-up code, handle errors of different libraries consistently

a program may use other libraries, each library may have their own error code range and they overlap, the group id allows to distinguish between them

gotos in c are local to the current routine. for example it is not possible with goto to jump between routines

general overview of error handling strategies: error handling

example using the reference implementation from sph-sc-lib:

#include "sph/status.c"

status_t test() {
  status_init;
  if (1 < 2) {
    int group_id = 123;
    int error_id = 456;
    status_set_both_goto(group_id, error_id);
  }
exit:
  return status;
}

int main() {
  status_init;
  // code ...
  status_require(test());
  // more code ...
exit:
  return status.id;
}

memory management

use the local memory pattern with integer error codes

benefit: free all allocations up to point easily

register memory allocations on the stack. example using the reference implementation from sph-sc-lib

#include "sph/local-memory.c"

int main() {
  local_memory_init(2);
  int* data_a = malloc(12 * sizeof(int));
  if(!data_a) goto exit;  // have to free nothing
  local_memory_add(data_a);
  // more code ...
  char* data_b = malloc(20 * sizeof(char));
  if(!data_b) goto exit;  // have to free "data_a"
  local_memory_add(data_b);
  // ...
  if (is_error) goto exit;  // have to free "data_a" and "data_b"
  // ...
exit:
  local_memory_free();
  return(0);
}

local_memory_init(size) allocates an address register on the stack

local_memory_add(address) adds a pointer to the register

local_memory_free() frees all pointers added so far

general

heap memory is requested when needed and gets reserved for the program (allocation). if the reservation is not ended when the memory is not needed anymore (deallocation), then the memory consumption of a process can grow continually over time. this is called memory leak.

this prevents programs from running indefinitely, as at some point all the available memory will be reserved and the program can not request additional memory

each allocation must be deallocated explicitly at some point in the execution of the program or implicitly with the end of the process, where all process memory is freed automatically

tools like valgrind can help to trace and find memory leaks

calling free on a null pointer: not a problem. calling free on a pointer with an already freed address: error

heap and stack

the stack is memory space that is reserved automatically for the extend of a routine call, for example to store routine arguments and local variables. it has a limited or fixed size. heap memory is all other available system memory

life time

how long a memory area is needed may depend on arbitrary conditions. the condition might for example be dependent on current variable scope. but not always is memory needed only in a single routine, and often pointers with addresses are given to other routines or returned

example cases

routine returns a pointer and the developer needs to know that the memory should be freed eventually (delegates the responsibility for the reservation)

routine receives a pointer and frees it at some point (takes over the responsibility for the reservation)

non-local jumps / exceptions occur moving the flow of execution to other routines in the program and context including reserved memory addresses is lost

other

output arguments

routines can pass new values to the caller in two ways: via return and via references (output arguments)

memory addresses (of stack of heap memory) are passed with pointer arguments to a routine. the routine then sets results at the referenced locations. in this way multiple result values can be created

project directory structure

examples _

prefer local variables to a set of globals

it might save declaration overhead, but access of a local is often faster because the compiler can predict where it is modified and prepare to cache it

performance example

global

0m10.745s

0m10.739s

local

0m9.931s

0m9.940s

information encapsualtion

c does not have a module system and routines can generally not be defined inside a limited scope inside a compilation unit. there is a notion of file scope, but for this multiple objects have to be compiled and linked

the most basic helpful thing is perhaps namespacing with semantic identifier construction. construct identifiers from words with prefixes, for example "modulename-routinename", to group them from generic to the specific

advanced solutions in existence include module systems that pre-process c-code


tags: programming start q1 document guide pattern computer c design