QUOTE: Life is a journey, not a destination.

The C Programming Language Usage Guide

Some basic concepts about the programming language C

31. Mar 2024 toc: disabled view: slim

Tips

  • Use void in functions without parameters. For example: int main(void) { ... }
  • Use #pragma once at the beginning of a header file to prevent multiple inclusion.
  • Use the compiler flags -Wall, -Wextra and -Werror for additional hints.
  • Use the compiler flag -g to be able to debug your program with gdb.
  • Use the compiler flag -rdynamic the keep function names in binary exectuables.

Error Handling

Signal Handling

Create a function which handles the signal.

void handle_sigint() {
  // do nothing
}

Register the signal handler with sigaction().

struct sigaction sigint_action;
sigint_action.sa_handler = handle_sigint;
sigint_action.sa_flags = 0;
sigemptyset(&sigint_action.sa_mask);
sigaction(SIGINT, &sigint_action, NULL);

Segfault Backtracing

Create a function which handles the signal. Register the signal handler with sigaction() to the SIGSEGV signal. Specify a list of a certain length which will later correspond to the levels of backtracing. Print the backtrace with backtrace_symbols_fd() and call exit to stop the program.

void handle_segfault(int signal) {
  void *list[10];
  size_t size = backtrace(list, 10);
  fprintf(stderr, "ERROR: signal %d:\n", signal);
  backtrace_symbols_fd(list, size, STDERR_FILENO);
  exit(1);
}

The compiler flag -rdynamic is required in order to view the function names in the backtrace.

Localization

setlocale(LC_ALL, "de_DE.UTF-8");

Make sure you have the desired language installed on your system.

Use a custom shared library

The shared library gets linked during runtime. A shared library should start with the prefix lib.

  1. Copy the header file to /usr/local/include/.
  2. Compile the shared library with cc -o libtest -shared -fPIC libtest.c.
  3. Copy the shared library to /usr/local/lib/.
  4. Add the folder to /etc/ld.so.conf if the entry doesn't already exist.
  5. Run ldconfig to load the library. Check with ldconfig -p | grep libterm if the library was loaded correctly.
  6. Include the library with #include <libtest.h>.
  7. Compile your application with cc -o main -ltest main.c. Run ldd main to see if the library is listed as dependency.

The following Makefile show how to create simple install/uninstall options:

Makefile

default: build

build:
  cc -o libtest -shared -fPIC libtest.c

install: build
  sudo cp libtest.h /usr/local/include/
  sudo cp libtest.so /usr/local/lib/
  sudo ldconfig
  rm libtest.so

uninstall:
  sudo rm /usr/local/include/libtest.h
  sudo rm /usr/local/lib/libtest.so

clean:
  rm libtest.so

Java Native Interface

The JNI (Java™ Native Interface) allows us to implement native methods in the programming language C.

Install the JNI header files by symlinking jni.h and linux/jni_md.h to /usr/local/include.

ln -s /usr/lib/jvm/java-<version>-openjdk/include/jni.h jni.h
ln -s /usr/lib/jvm/java-<version>-openjdk/include/linux/jni_md.h jni_md.h

The following code snippet serves as an example to demonstrate the implementation of native methods in the programming language C.

public class Example {
  
  static {
    System.loadLibrary("native");
  }

  public static void main(String[] args) {
    final Example example = new Example();
    final int sum = example.sum(3, 5);
    System.out.println("sum: " + sum);
  }

  private native int sum(int a, int b);
}

There are two important things to notice in this snippet. First, we have a static block which loads the library native. This will be the name of our shared library we will later create. Keep in mind the name should be the same as the filename (libnative.so). And second, the method sum has the modifier native which tells the JVM this method is a native implementation.

The next step is to create a header file we can later use to implement out our native method in C. In order to create a header file from our Java files we can use the following command.

javac -h . Example.java

After that, we have to new files, an Example.class file and an Example.h file. If we look into the the header file we can see the following method definition.

/*
 * Class:     Example
 * Method:    sum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_Example_sum
  (JNIEnv *, jobject, jint, jint);

Now we can implement our sum method by creating a new Example.c file, including the header file and writing the following implementation.

#include "Example.h"

JNIEXPORT jint JNICALL Java_Example_sum(JNIEnv *env, jobject this, jint a, jint b) {
  return a + b;
}

We are all set and can now create our shared library with the following command which will generate our libnative.so file.

cc -o libnative.so -shared -fPIC Example.c

After that, we can execute our Java program by passing the directory location of our shared library to the java command.

java -Djava.library.path=./ Example.java

Now you should see the output sum: 8 on your screen. Congrats, you implemented a Java method in the programming language C!

You can find more information about implementing native methods with C and JNI here.

Libraries

  • curl: HTTP Client library
  • libpq-fe: Postgres Client library
  • openssl: SSL library
  • cmark: Markdown library
  • toml: TOML Parser libarry

Tools

  • man: View manuals for a specific topic
  • xxd: View binary files as hex
  • ldd: View binary dependecies
  • file: View file types

Books