Showing posts with label c. Show all posts
Showing posts with label c. Show all posts

Wednesday, June 17, 2026

Interrupt Deadlock

In embedded systems, there is a dangerous trap where standard thread safety fails completely, leaving your application permanently frozen. That trap is to use a mutex inside an interrupt handler. Consider this thread-safe function, where you protect shared state with a mutex:

void update_hardware(int data) {
    pthread_mutex_lock(&lock)
    global_hardware_buffer = data; // What if an interrupt hits right here?
    pthread_mutex_unlock(&lock);
}

If this function is called by an interrupt service routine (ISR) and a hardware interrupt fires right in the middle of that function:

  1. The Main Thread acquires the lock.
  2. The Interrupt hits. The CPU immediately freezes the main thread and jumps to your ISR.
  3. The Re-entry: The interrupt handler needs to log something, so it calls update_hardware().
  4. The Deadlock: The interrupt handler hits pthread_mutex_lock(). It sees the lock is busy, so it waits.

But who is it waiting for? It's waiting for the main thread to release the lock. Except the main thread is frozen underneath the interrupt handler, waiting for the interrupt to finish!

A common misconception is that the operating system's scheduler will see the interrupt handler is blocked, context-switch it out, let the main thread finish, and fix the mess. It can't. In embedded systems or Real-Time Operating Systems (RTOS), interrupts run at a higher execution priority than the scheduler itself. ISRs execute outside normal thread scheduling. If an ISR attempts to wait for a resource held by the interrupted thread, forward progress becomes impossible because the interrupted thread cannot run until the ISR completes. This is priority inversion taken to the extreme. The ISR (highest priority in the system) ends up waiting for a lower-priority thread that it itself has preempted.

To survive interrupts, your code cannot just be thread-safe, it must be reentrant. A reentrant function is completely self-contained. It never touches global variables, it never uses static buffers, and it never locks a mutex. It relies strictly on local variables allocated on the stack or parameters passed to it. Because it has no shared memory between calls, it can be interrupted at any instruction and safely called again without data corruption or deadlocks. Here is a reentrant function example:

// This function operates purely on local stack memory.
// It can be safely interrupted and re-entered at any microsecond.
int calculate_hardware_state(int current_state, int new_data) {
    int next_state;
    next_state = current_state + new_data;
    return next_state;
}

Also pay attention to macros because if a macro references a global variable, a static variable, or a hardcoded hardware register under the hood, any function using that macro instantly becomes non-reentrant:

int global_status = 0;
#define SET_STATUS_FLAG(mask) (global_status |= (mask))

Never use blocking synchronization primitives (like mutexes, malloc, or I/O) inside an interrupt handler or signal handler. If you must pass data between an interrupt and your main loop, stick to lock-free mechanisms like C11 atomics or volatile flags.

Wednesday, October 1, 2025

Why C/C++ circular dependency causes "syntax error"

I have a headerA with methodA that uses a structB from headerB and therefore includes headerB. When I call methodA, I get a "syntax error" in headerA where methodA uses structB location. Details:
// headerB.h
#ifndef HEADER_B_H
#define HEADER_B_H
struct structB { int x; };
#endif

// headerA.h
#ifndef HEADER_A_H
#define HEADER_A_H
#include "headerB.h"
void methodA(structB param);
#endif

// main.cpp
#include "headerB.h" // headerB processed first
#include "headerA.h" // headerB already included, so HEADER_B_H is defined
                     // #include "headerB.h" does NOTHING
                     // structB is unknown! → SYNTAX ERROR
The "syntax error" occurs because the compiler doesn't know what structB is when it tries to compile methodA. If headerB includes headerA (directly or indirectly), you have a circular dependency. The C++ preprocessor just does text substitution - it doesn't understand C++ syntax. It can't detect circular dependencies because include guards prevent infinite loops, so the circular dependency becomes an incomplete type error instead.

Monday, June 16, 2025

C/C++ header mismatch bug

I encountered a problem where a field in a global struct (myStruct) held a valid value before entering a function foo, but turned into garbage after entering it. When I consulted AI tools, they suggested that foo might be allocating very large local arrays, causing a stack overflow that could corrupt the global structure. Another possibility was an out-of-bounds write elsewhere in the code.

After a week of debugging and trying various solutions—such as increasing the thread's stack size—I discovered the root cause: The function foo was defined in a C library with multiple versions. Each version resided in a different folder but had the same file names. Which folder was used depended on a #define. I was including the header from one version of the library, but linking against the implementation from another. If the struct definitions had matched, this wouldn’t have caused an issue, but they differed—evident from the differing sizeof(myStruct). As a result, myStruct was interpreted using the wrong layout, leading to corrupted values from an incorrect memory region.

Wednesday, January 29, 2025

Linux: Dynamic vs Static Linking

Today, we had a chat with a colleague about whether a C/C++ binary (ELF) built on one Linux distribution would work on another. After some research, I found that default GCC builds are dynamically linked, and the ELF file contains:

  1. Your program's code
  2. A list of dynamic dependencies (shared libraries) it needs
  3. Symbols that need to be resolved at runtime
However, it does not contain the actual shared libraries - those need to be present on the system where you run the program. You can see these dependencies using ldd. For example, a simple C "hello world" program with only a printf() call can be built with gcc hello.c -o hello. ldd hello output:
linux-vdso.so.1 (0x00007fff6a0e1000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdaf6400000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdaf66b3000)

The files size is 15960 bytes. To make is truly independent of any shared library we would build it with gcc -static hello.c -o hello_static. ldd hello_static shows:
not a dynamic executable
The size of hello_static is a whopping 900344 bytes, 56X more than the dynamically linked build.

The good news is that these libraries are present by default on virtually every Linux distribution, so including them in every executable would waste a lot of space. However, you must ensure that C++ version (C++17, C++20, etc.) specific features used in your code are supported by the gcc/g++ version on the target Linux distribution. Of course, the CPU architecture has to be the same too — that goes without saying.

On Windows, C++ libraries come with Visual C++ Redistributable:

Note that  if a Windows PC can successfully load and run a DLL compiled with a specific C++ version, it can also run an EXE compiled with that C++ version because the C++ runtime requirements are the same whether the code is in a DLL or EXE. The only difference is how the code is packaged and loaded, not its runtime requirements.

Thursday, January 9, 2025

Memory Insights from a Segfault

Recently, a program crashed with segmentation fault (sigsegv). Since segmentation faults can happen for a variety of reasons, it took some time to find out that the root cause was insufficient stack (stack overflow) due to a function allocating extra 16kB for a local variable, float var[4096]. The quick fix was to increase the stack size of the thread calling that function from 32kB to 1MB. 

Another solution is to move var to static storage instead of using the stack by changing the definition to static float var[4096]. Upon closer inspection of the code, I saw that var was just used to copy a static array before sending it to another function. Since that function was not modifying the array, there was no reason for the copy. Removing var removed large stack allocation. 

Problems like this are stressful in the short term but provide an opportunity to review the concept of memory regions. Here's a concise breakdown:

Static Storage
- Memory allocated at program start, lives for entire program duration
- Size must be known at compile time 
- Good for: fixed-size buffers that exist for whole program 
- Example: static uint8_t buffer[1024];
- Zero runtime allocation overhead 
- Can't be resized 

Stack
- Memory allocated/deallocated automatically when entering/leaving scope 
- Very fast allocation/deallocation - Limited size (often few MB) 
- Good for: small-to-medium temporary buffers 
- Example: void foo() { uint8_t temp[1024]; }
- Risk of stack overflow with large allocations

Heap 
- Dynamic runtime allocation 
- More flexible but slower than stack 
- Larger size available 
- Good for: large buffers or unknown sizes 
- Example: auto* buffer = new uint8_t[1024];
- Risk of fragmentation 
- Must manually manage memory 

For embedded systems with fixed-size allocation needs: 
1. Use static storage for long-lived, known-size buffers 
2. Use stack for small temporary buffers 
3. Consider static memory pools instead of raw heap allocation if you need dynamic allocation

In Visual Studio 2022, you can use Build > Run Code Analysis to detect functions with large stack allocations, they will be marked with C6262 excessive stack usage warnings.

Sunday, September 22, 2024

Using code from Simulink model in HIL

Steps of converting a Simulink model to code usable in Hardware-in-the-loop (HIL) simulation:
  1. Checkout/pull Simulink model from repository to your local.
  2. Run Simulink model and confirm it finishes as expected. If not, inform the model maintainer and ask them to commit/push the model with correct settings to repo. 
  3. Confirm that C/C++ code can be generated from model. Sometimes an s-function build file (mexw64) exists but its source code is missing, which allows the model to run but prevents code generation.
  4. Copy code to Visual Studio and verify that you can build and run it. There are cases where Simulink is more forgiving of errors like uninitialized variables, or missing #include <cmath> but Visual Studio cannot build the code.
  5. Copy code to real time Linux PC and verify code can be build there too.
  6. Commit code to its own repo.
  7. Run HIL with new code and verify HIL works as expected.

Thursday, December 28, 2023

The unbearable lightness of C

We have a Simulink project from which I generate C code to use in a Visual Studio C++ project. The Simulink project works fine, I can build the C code without any errors, but when I ran the C executable, I got an access violation error due to trying to write to address 0x0. The C project was working fine for previous versions. 

I initially identified the revision where this error first appeared. I reviewed the changed code and couldn't find anything wrong.

After a week of debugging I found out that it was due to an off-by-one error; An array was defined with size 47 using Simulink function ssSetNumDiscState(S, 47) in mdlInitializeSizes(...), but later in function mdlInitializeConditions(...), a for loop with upper bound of 48 was executed which resulted in writing to the memory adjacent to the allocated section for that array. 
static void mdlInitializeConditions(SimStruct *S) {
    real_T *states = ssGetRealDiscStates(S);
    for (int i=0; i < 48; i++) {
    	*(states + i) = 0;
    }
}
It has nothing to do with the latest change in the sense that it was not related to the logic of the change. Instead, that change altered the memory mapping of the build, putting another array (TUBufferPtr) after the first and the overflow caused the value at TUBufferPtr[0] to become 0x0 (NULL). When the program tried to write to the address represented by TUBufferPtr[0], it naturally caused an access violation because writing to address 0x0 is not allowed.
When I looked at the repository history, I saw that this error was introduced 3 years ago and for all these years, it did not become visible! The access violation only occurred when other unrelated code updates caused the compiler to arrange memory slightly differently.

This is also one of the reasons why sometimes C programs behave differently between debug and release builds or on different versions (service packs) of the same operating system. That discrepancy is an indication of an error hiding somewhere in your program. Another way such an error can become visible is when you have it in your C++ DLL that you call from your Java program. One day you update your JDK and your program crashes because DLL and JVM share the same memory space. Naturally, your first inclination is to blame the JDK update but in reality there is a buffer overflow in your DLL code.

You can never say for sure that modifying a code in module A won't have an effect on an unrelated module B. As long as these modules are in the same process, i.e. use the same memory, the compiler might put them side by side and an error in A can overflow to B. Functionally distant modules can become "close relatives" in memory address space.

In a way, I was fortunate that the overwrite contained zero values. If it had used some value that was a valid address for the application to write to, it would cause seemingly random behavior and would have been a lot more fun (!)

Note that a typical static code analyzer would not able to catch this problem because we are defining the data structure with Simulink specific ssSetNumDiscState and getting it with ssGetRealDiscStates functions.

I solved the problem by adding #define NUMBER_OF_DISCRETE_STATES (47) and using that define in both ssSetNumDiscState() and mdlInitializeConditions(). This case study also serves as a cautionary tale illustrating why you should use defined constants rather than magic numbers.

In my nightly automated tests I was only checking if exe was generated which only proved that it compiled. I added running the exe and checking if it finished successfully because an access violation can only occur at runtime.

Friday, November 3, 2023

Simulink signal

After generating code from a Simulink model, if you want to read the value of a parameter inside the model which is not an output port, you can use signals. In Matlab R2022b, signal definition is different from R2019a. The following R2022b method (assuming you have Embedded Coder) is only valid if you generate C code, I have not been able to find a way to generate signal code for C++:
  1. Open Simulink model
  2. Open Configuration Parameter window, go to Code Generation
  3. System target file: ert.tlc (Embedded Coder)
  4. Language: C
  5. In Simulink APPS menu, click on Embedded Coder, A C CODE tabs appears to the right of APPS menu
  6. Find the signal line you want the generate code for and double click on it, give it a name, e.g. "mySignal"
  7. Single click on signal line, a pop up will appear, click on the left-most item, "Add selected signals to code mappings"
  8. Under C CODE tab, click on Code Interface and then click Default Code Mappings
  9. A Code Mappings panel will appear at the bottom of model
  10. Click on right most tab, Signals/States
  11. Under Signals, find mySignal
  12. Click to the right of mySignal (Store Class column), change Auto to Model Default.
  13. Generate code by pressing ctrl + b
  14. If your model name was MyModel, the generated code will have a file called MyModel_ert_rtw/MyModel.h containing the data structure MyModel_B which has mySignal as a field that you can access from your own C/C++ code. 
  15. If your code is C++, dont forget to use include in C wrapper: extern "C" {#include "MyModel_ert_rtw/MyModel.h"}

Thursday, January 12, 2023

C++: Using non-standard data types

When you use non-standard data types like enum, bool, double, long double, it might become a problem if you are sending data to another platform, e.g. sending from x86 to ARM, or even when sending from Windows to Linux. For example, long double is 8 bytes in MSVC but 16 bytes in GCC. These size differences will result in errors when casting the received bytes to data. Always know non-standard data type lengths of the platform that you are communicating with.

Wednesday, January 11, 2023

C++: Simple serialization

Simple serialization demo by converting struct to char (byte) array:

Monday, January 9, 2023

C: char pointer vs array

In C, the difference between a char pointer and char array becomes clear when you want to do assignment:

  #include<stdio.h>
  #include<string.h> //for strcpy
  int main() {
      char s[][10] = {"aaa", "bbb", "ccc"};
      //char* s[] = {"aaa", "bbb", "ccc"}; //same as above
      char* t1 = s[1];
      char t2[10]; strcpy(t2, s[1]);
      //t2 = s[1];// error: assignment to expression with array type
      printf("s[1] = %s\n", s[1]);
      printf("t1 = %s\n", t1);
      printf("t2 = %s\n", t2);
      return 0;
  }

s[1] is a char pointer. You can assign a char pointer to another char pointer but you cannot assign a char array to char pointer, you have to use strcpy. 

Another interesting difference:

    char a[] = "string1"; 
    char *p1  = a;
    char *p2  = "string2";
    a[0] = 'z';
    printf("a[0] = %c\n", a[0]);
    p1[0] = 'k'; //works fine, changes a[0]
    printf("a[0] = %c\n", a[0]);
    p2[0] = 'm'; //results in segmentation fault
    printf("p2[0] = %c\n", p2[0]);

Friday, December 9, 2022

C++: Using constant for array size

In C, you can use a constant when declaring an array:

  const n = 5;
  int a[n];

In C++, it would result in compile error "constant expression required". You have to declare it using constexpr:

  constexpr int n = 5;
  int a[n];

However, if you for example want to use the size of a vector to declare an array, you can't use the stack, you have to use the heap:

  int* a = new int[myVector.size()];
  ...
  delete[] a;

Tuesday, June 21, 2022

Stop build when "C4013 ... undefined; assuming extern returning int"

Recently I had to deal with a bug in a C project whose root cause was fabs not functioning properly due to missing #include <math>. Finding the root cause involved diving into more than 4 layers of abstractions, it was not fun (!) When run from Visual Studio as a dll project, the code resulted in fabs(10) >= fabs(20) to be true! When I compiled the project with Visual Studio, it gave "warning C4013: 'fabs' undefined; assuming extern returning int". I would normally expect the build to fail in the linking stage. Probably the linker is able to find the fabs but since it was first assumed to be returning int (4 bytes) and fabs returns double (8 bytes), this will result in 4 bytes of the return value being ignored, which can cause funny values. For more about the reason why this behavior is allowed, see my similar trouble with malloc before.

Another interesting fact is that the code was working properly when run from a Simulink s-function. I assume Simulink includes math.h somewhere in its hierarchy. Here is the simplified code:

To prevent Visual Studio from building when 4013 warning exists, go to project Properties - C/C++ - Advanced - Treat Specific Warnings as Errors - Add 4013:



Tuesday, November 2, 2021

memcpy() vs memmove()

When you need to implement removal of an element from an arraylist, don't use memcpy, use memmove. Your program might run fine on Windows but cause trouble in Linux, whose root cause would be hard to find.


memcpy() vs memmove(): in case of memcpy(), there is no extra buffer taken for source memory. The copying is done directly on the memory so that when there is memory overlap, we get unexpected results. 

Friday, September 11, 2020

Printing all permutations of a string

You can use the following C code to print all two character permutations (with repetition allowed) of a string:

If you would like to print three character permutations, you have to add another for loop:

As you can see, this is not generalizable because you have to keep adding or deleting for loops manually. Also notice that with each loop, the p[] index is increased by one. We could use this fact to write a recursive function that could handle all character permutations:

This is a nice example of first writing the simple cases, seeing a pattern and reaching the general case, i.e. inductive reasoning.

Monday, March 9, 2020

Stack corruption due to different pointer types

The C programming language allows you to send a float pointer to a function that expects a double pointer, which causes stack corruption. Example code:
When you run it, you will see that the val_f2 is zero (should be 5):

Visual Studio 2015 will only display stack corruption message when you build in debug mode. In release mode, you don't get a message.

If you copy the same code to a cpp file, Visual Studio will use the C++ compiler and it will not build the code, saying that types are incompatible.