programming-languages

Function

Function Parameter

// does not accept any params
int foo(void) { ... } 
 
// does accept an arbitrary number of parameters
int foo() { ... } 

Function Call

Function Call By Value

Function Call By Reference

Important Functions

String functions:

FunctionDescription
strlenlength of the string (excl. terminating \0)
strstrlocates a substring
strcpycopies a string
strncpycopies a string with a size limit (copies at most n bytes)
strdupduplicates a string
strndupduplicates a string with a size limit (copies at most n bytes)
strcmpcompares strings
strncmpcompares strings up to n characters
strtolstring long conversion
strtollstring long long conversion
strtofstring float conversion
strtodstring double conversion
strtoldstring long double conversion
strerrorconverts an error code into a human-readable string

IO functions:

FunctionDescription
openopening (and possibly creating) a file
readreading from a file
writewriting to a file
closeclosing a file
FunctionDescription
freadreads elements, each bytes long
fgetsreads a line (up to '\n')
fgetcreads a character
fwritewrites elements, each bytes long
fputswrites a C-string
fputcwrites a character
fprintfformatted printing
fseekset the file position indicator
getlinereads a line into a dynamically allocated buffer
ferrortests the error indicator of the stream (0 = no errors)
feoftests if the stream reached end-of-file (0 = not reached)
clearerrresets error and end-of-file indicators
filenoreturns the file descriptor of a stream
e.g. fileno(stdout) = 1
fflushenforces writing of buffered data to the file
fcloseflushes the stream and closes it

Keyword

static

#include <stdio.h>
 
void counter() {
    // initialization happens only once at program start
    static int count = 0; 
    
    // without 'static', 'count' would reset to 0 every time
    count++;
    
    printf("Function has been called %d times\n", count);
}

Taken from StackOverflow 1.

Usually, you will see the static keyword in these places:

  1. A static variable inside a function keeps its value between invocations.
  2. A static global variable or function is “seen” only in the file in which it’s declared.

volatile

Taken from StackOverflow 2.

volatile tells the compiler not to optimise anything that has to do with the volatile variable.

There are at least three common reasons to use it, all involving situations where the value of the variable can change without action from the visible code:

  • When you interface with hardware that changes the value itself
  • when there’s another thread running that also uses the variable
  • when there’s a signal handler that might change the value of the variable.

Let’s say you have a little piece of hardware that is mapped into RAM somewhere and that has two addresses: a command port and a data port:

// Source - https://stackoverflow.com/a/246148
// Posted by Nils Pipenbrinck, modified by community. See post 'Timeline' for change history
// Retrieved 2025-12-10, License - CC BY-SA 4.0
 
typedef struct
{
  int command;
  int data;
  int isBusy;
} MyHardwareGadget;

Now you want to send some command:

// Source - https://stackoverflow.com/a/246148
// Posted by Nils Pipenbrinck, modified by community. See post 'Timeline' for change history
// Retrieved 2025-12-10, License - CC BY-SA 4.0
 
void SendCommand(MyHardwareGadget* gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isBusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

Looks easy, but it can fail because the compiler is free to change the order in which data and commands are written. This would cause our little gadget to issue commands with the previous data-value. Also take a look at the wait while busy loop. That one will be optimised out. The compiler will try to be clever, read the value of isBusy just once and then go into an infinite loop. That’s not what you want.

The way to get around this is to declare the pointer gadget as volatile. This way the compiler is forced to do what you wrote. It can’t remove the memory assignments, it can’t cache variables in registers and it can’t change the order of assignments either.

This is the correct version:

// Source - https://stackoverflow.com/a/246148
// Posted by Nils Pipenbrinck, modified by community. See post 'Timeline' for change history
// Retrieved 2025-12-10, License - CC BY-SA 4.0
 
void SendCommand(volatile MyHardwareGadget* gadget, int command, int data)
{
  // wait while the gadget is busy:
  while (gadget->isBusy)
  {
    // do nothing here.
  }
  // set data first:
  gadget->data    = data;
  // writing the command starts the action:
  gadget->command = command;
}

Macro

Parameterised Macro

#define NRELEMENTS(a) (sizeof(a) / sizeof(a[0]))

Conditional Macro

  • #ifdef MY_HEADER_H
  • #ifndef MY_HEADER_H
  • #if DEBUG >= 2
  • #else
  • #endif

Examples:

#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
#if DEBUG >= 2
printf("debug, debug\n");
#endif

Multi-Statement Macro

#define CMDS \
do { \
	a = b; \
	c = d; \
} while(0)

Pointer

void*

void* is a pointer with no data type.

int i;
int *pint;
void *pvoid;
 
pvoid = (void *) &i;
pint = (int *)pv;

Function Pointer

#include <stdio.h>
 
int add(int a, int b) { 
    return a + b; 
}
 
int sub(int a, int b) { 
    return a - b; 
}
 
int main() {
    int (*f)(int, int); 
    int ret;
 
    // --- Scenario A: Explicit Syntax ---
    // we explicitly take the address of the function using '&'
    f = &add;
    ret = f(42, 23); 
    printf("Add result: %d\n", ret); // Output: 65
 
 
    // --- Scenario B: Implicit Syntax (Decay) ---
    // function names 'decay' into pointers automatically, so '&' is optional.
    // the user snippet noted that explicit assignment is "better" (more readable).
    f = sub; 
    ret = (*f)(42, 23); 
    printf("Sub result: %d\n", ret); // Output: 19
 
    return 0;
}

Tagged Union

typedef enum {
    TYPE_INT,
    TYPE_FLOAT,
    TYPE_STRING
} ValueType;
 
typedef union {
    int32_t i_val; // using stdint.h type
    float   f_val;
    char* s_val;
} ValueData;
 
typedef struct {
    ValueType type;
    ValueData data;
} TaggedValue;
 
void print_value(TaggedValue v) {
    switch (v.type) {
        case TYPE_INT:    printf("Integer: %d\n", v.data.i_val); break;
        case TYPE_FLOAT:  printf("Float: %.2f\n", v.data.f_val); break;
        case TYPE_STRING: printf("String: \"%s\"\n", v.data.s_val); break;
        default:          printf("Unknown\n");
    }
}
 
int main() {
    TaggedValue v1 = { 
        .type = TYPE_INT, 
        .data.i_val = 100 
    };
    print_value(v1);
    
    print_value((TaggedValue){ 
        .type = TYPE_FLOAT, 
        .data.f_val = 99.99f 
    });
 
    print_value((TaggedValue){ 
        .type = TYPE_STRING, 
        .data.s_val = "C99 Literals" 
    });
 
    return 0;
}

Operator

Ternary Operator

int a = 10, b = 20;
int max = (a > b) ? a : b;

Comma Operator

#include <stdio.h>
 
int main() {
    int a = 10;
    int b = 20;
    int result;
 
    // 1. 'a += 5' executes (a becomes 15). Result (15) is discarded.
    // 2. 'b + 10' executes (20 + 10 = 30).
    // 3. 'result' is assigned 30.
    result = (a += 5, b + 10);
 
    printf("a: %d (side effect applied)\n", a);
    printf("result: %d (value of second expression)\n", result);
 
    return 0;
}

Important Headers

  • stdlib.h
    • malloc()
    • calloc()
    • free()
  • stdio.h
  • unistd.h
  • stdbool.h
  • string.h
    • strlen()
    • strcpy()
  • sys/mman.h
  • semaphore.h
  • fcntl.h
    • system calls for file handling

Example

Argument Parsing via getopt

Simple grep program:

usage: ./mygrep [-i] [-o outfile] keyword [file...]
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
void printUsage(char *program) {
  fprintf(stderr, "usage: %s [-i] [-o outfile] keyword [file...]\n", program);
  exit(EXIT_FAILURE);
}
 
typedef struct {
  bool ignore_case;
 
  char *output_file;
 
  char *keyword;
 
  char **files;
  int files_len;
} options_t;
 
bool parse(int argc, char *argv[], options_t *opts) {
  opts->ignore_case = false;
  opts->output_file = NULL;
 
  int opt;
  while ((opt = getopt(argc, argv, "io:")) != -1) {
    switch (opt) {
    case 'i':
      opts->ignore_case = true;
      break;
 
    case 'o':
      opts->output_file = optarg;
      break;
 
    default:;
      return false;
    }
  }
 
  int offset = optind;
  if (offset >= argc) {
    // keyword is required
    return false;
  }
  opts->keyword = argv[offset];
  offset++;
 
  if (offset < argc) {
    // files are optional
    opts->files = argv + offset;
    opts->files_len = argc - offset;
  }
 
  return true;
}
 
int main(int argc, char *argv[]) {
  options_t opts = {0};
  if (!parse(argc, argv, &opts)) {
    printUsage(argv[0]);
    return EXIT_FAILURE;
  }
  
  ...
}

The interesting part is:

getopt(argc, argv, "io:")

where i has no input, but o has (denoted using a successive `:).

Another example:

getopt(argc, argv, "n:w:")

where n and w take one argument.

Error Handling

#include <stdio.h>
 
FILE *file;
if ( (file = fopen("input.txt", "r")) == NULL ) {
	fprintf(stderr, "[%s] ERROR: fopen failed: %s\n", argv[0], strerror(errno));
	exit(EXIT_FAILURE);
}

Signal Handling

manpages

The sigaction structure is defined as something like:

struct sigaction {
	void     (*sa_handler)(int);
	void     (*sa_sigaction)(int, siginfo_t *, void *);
	sigset_t   sa_mask;
	int        sa_flags;
	void     (*sa_restorer)(void);
};
#include <stdio.h>
#include <string.h>   
#include <signal.h>
#include <unistd.h>  
 
void handle_signal(int signal) {
    const char msg[] = "\nCaught SIGINT! (Ctrl+C)\n";
    write(STDOUT_FILENO, msg, sizeof(msg) - 1);
}
 
int main(int argc, char *argv[]) {
    // allocate the configuration structure on the stack
    struct sigaction sa;
 
    // initialize memory to zero
	//
    // This is vital as garbage in 'sa_mask' 
    // or 'sa_flags' causes undefined bugs.
    memset(&sa, 0, sizeof(sa));
 
    // configure the struct
    sa.sa_handler = handle_signal; 
    
    // (optional) set flags here, e.g.:
    // sa.sa_flags = SA_RESTART; 
 
    // register the action
	//
    // we map sigint (ctrl+c) to our 
    // structure '&sa'
	//
    // we pass null for oldact because we don't
    // care about the previous state
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("Error configuring signal");
        return 1;
    }
 
    printf("Program running. Press Ctrl+C to test the handler...\n");
 
    // keep the program alive to receive signals
    while (1) {
        sleep(1); 
        write(STDOUT_FILENO, ".", 1); // visual heartbeat
    }
 
    return 0;
}

manpages

An async-signal-safe function is one that can be safely called from within a signal handler. Many functions are not async-signal-safe. […] In general, a function is async-signal-safe either because it is reentrant or because it is atomic with respect to signals (i.e., its execution can’t be interrupted by a signal handler).

Instead of exit(), call _exit() (exit immediately). Other signal-safe functions:

  • abort
  • exit
  • write
  • sigaction,

To avoid conflicts, you can do the following:

volatile sig_atomic_t quit = 0;
void handle_signal(int signal) {
	quit = 1;
}
 
int main(int argc, char **argv) {
	... // set up signal handler
 
	while (!quit) { /* main application loop */ }
	
	... // additional cleanup
	 
	return 0;
}
  • Why volatile? To avoid optimisations
  • Why sig_atomic_t? So signals cannot interfere with read/write of the flag

Shared Memory

  1. Initialisation:
    1. int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    2. ftruncate(fd, sizeof(shared_data_t)
    3. shared_data_t *data = mmap(NULL, sizeof(shared_data_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  2. Usage: access data
  3. Cleanup:
    1. munmap(data, sizeof(shared_data_t));
    2. close(fd);
    3. shm_unlink(SHM_NAME)
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
 
#define SHM_NAME "/example_shm_memory"
 
typedef struct {
  int counter;
  char message[128];
} shared_data_t;
 
int main() {
  // O_CREAT: create if not exists
  // O_RDWR: read/write access
  // 0666: permissions (rw-rw-rw-)
  int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
  if (fd == -1) {
    perror("shm_open");
    exit(1);
  }
  
  // set the size of the shared memory object
  if (ftruncate(fd, sizeof(shared_data_t)) == -1) {
    perror("ftruncate");
    exit(1);
  }
 
  // map the shared memory into this process's address space
  shared_data_t *data = mmap(NULL, sizeof(shared_data_t),
                             PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (data == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }
 
  // read/write from/to shared memory
  // e.g.:
  for (int i = 0; i < 5; i++) {
    data->counter = i;
    sprintf(data->message, "Message number %d from writer", i);
    printf("[Writer] Wrote: %d\n", i);
    sleep(1);
  }
 
  // cleanup
  munmap(data, sizeof(shared_data_t));
  close(fd);
	
  // cleanup, probably reader-only
  if (shm_unlink(SHM_NAME) == -1) {
    perror("unlink");
    exit(1);
  }
}

Semaphores and Shared Memory

Semaphores and shared memory example:

#include <fcntl.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
 
#define SHM_NAME "/example_shm_memory"
#define SEM_NAME "/example_shm_mutex"
 
typedef struct {
  int counter;
  char message[128];
} shared_data_t;
 
void run_writer() {
  printf("[Writer] Starting...\n");
 
  //  create/open shared memory object
  // O_CREAT: Create if not exists
  // O_RDWR: Read/Write access
  // 0666: Permissions (rw-rw-rw-)
  int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
  if (fd == -1) {
    perror("shm_open");
    exit(1);
  }
 
  // set the size of the shared memory object
  if (ftruncate(fd, sizeof(shared_data_t)) == -1) {
    perror("ftruncate");
    exit(1);
  }
 
  // map the shared memory into this process's address space
  shared_data_t *data = mmap(NULL, sizeof(shared_data_t),
                             PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (data == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }
 
  // open/create a semaphore for synchronization (mutex)
  // initial value 1 = unlocked
  sem_t *sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
  if (sem == SEM_FAILED) {
    perror("sem_open");
    exit(1);
  }
 
  // write loop
  for (int i = 0; i < 5; i++) {
    printf("[Writer] Waiting for semaphore...\n");
    sem_wait(sem); // Lock
 
    // critical section
    data->counter = i;
    sprintf(data->message, "Message number %d from writer", i);
    printf("[Writer] Wrote: %d\n", i);
 
    sem_post(sem); // Unlock
    sleep(1);      // Simulate work
  }
 
  // cleanup
  munmap(data, sizeof(shared_data_t));
  close(fd);
  sem_close(sem);
 
  printf("[Writer] Done.\n");
}
 
void run_reader() {
  printf("[Reader] Starting...\n");
 
  // open shared memory object (must exist or be created by writer)
  int fd = shm_open(SHM_NAME, O_RDWR, 0666);
  if (fd == -1) {
    perror("shm_open (run writer first!)");
    exit(1);
  }
 
  // map it
  shared_data_t *data = mmap(NULL, sizeof(shared_data_t),
                             PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (data == MAP_FAILED) {
    perror("mmap");
    exit(1);
  }
 
  // open semaphore
  sem_t *sem = sem_open(SEM_NAME, 0);
  if (sem == SEM_FAILED) {
    perror("sem_open");
    exit(1);
  }
 
  // read loop
  for (int i = 0; i < 5; i++) {
    printf("[Reader] Waiting for semaphore...\n");
    sem_wait(sem); // lock
 
    // critical section
    printf("[Reader] Read: %d, Text: '%s'\n", data->counter, data->message);
 
    sem_post(sem); // unlock
    sleep(1);
  }
 
  // cleanup & unlink
  // IMPORTANT: the last user (usually supervisor) should unlink
  munmap(data, sizeof(shared_data_t));
  close(fd);
  sem_close(sem);
 
  // unlink removes the name from the system
  shm_unlink(SHM_NAME);
  sem_unlink(SEM_NAME);
 
  printf("[Reader] Cleaned up and exiting.\n");
}
 
int main(int argc, char **argv) {
  if (argc > 1 && strcmp(argv[1], "reader") == 0) {
    run_reader();
  } else {
    run_writer();
  }
  return 0;
}

Circular Buffer

#include <stdbool.h>
 
#define BUFFER_SIZE 10
 
typedef struct {
    int read_head;
    int write_head;
    int count;
    int data[BUFFER_SIZE]; 
} buffer_t;
 
void buffer_init(buffer_t *buf) {
    buf->read_head = 0;
    buf->write_head = 0;
    buf->count = 0;
}
 
bool buffer_is_full(buffer_t *buf) {
    return buf->count == BUFFER_SIZE;
}
 
bool buffer_is_empty(buffer_t *buf) {
    return buf->count == 0;
}
 
bool buffer_write(buffer_t *buf, int value) {
    if (buffer_is_full(buf)) {
        return false;
    }
 
    buf->data[buf->write_head] = value;
    buf->write_head = (buf->write_head + 1) % BUFFER_SIZE;
    buf->count++;
 
    return true;
}
 
bool buffer_read(buffer_t *buf, int *out_value) {
    if (buffer_is_empty(buf)) {
        return false;
    }
 
    *out_value = buf->data[buf->read_head];
    buf->read_head = (buf->read_head + 1) % BUFFER_SIZE;
    buf->count--;
 
    return true;
}

Footnotes

  1. syntax - What does “static” mean in C? - Stack Overflow

  2. declaration - Why is volatile needed in C? - Stack Overflow