A block of code that is suppose to cause a bus error executes fine

Viewed 900

I am currently reading Expert C Programmign - Deep C Secrets. On page 164 where the author was explaining Bus Error and Segmentation fault , he showed this line of code

union { 
  char a[10];
  int i;
} u ;

int * p = ( int * ) &(u.a[1]);
*p = 17; /* the misaligned addr in p causes a bus error */

The above code is suppose to trigger a bus error, but when i ran it, it ran fine without any error. The author gave the below explanation

This causes a bus error because the array/int union ensures that character array "a" is also at a reasonably aligned address for an integer, so "a+1" is definitely not. We then try to store 4 bytes into an address that is aligned only for single-byte access. A good compiler will warn about misalignment, but it cannot spot all occurrences.

My understanding of the above statement is that , char is 1 byte and we are trying to place an int which is 4 byte on an index of char a[10] hence a bus error will occur ( am not sure if my understanding is right or wrong )

My question is why is the above code not causing a bus error.

Note: Am not a CS student,simple explanations will help.

Note: A question that looks smiliar to this question have already been asked, but am only specific to the above block of code.

3 Answers

I believe that the book is mistaken. The code causes undefined behaviour. Expecting any certain behaviour from it is therefore flawed. Also note that not all architectures can cause bus errors. If the book doesn't explain this fact, that doesn't speak for it either.

My understanding of the above statement is that , char is 1 byte and we are trying to place an int which is 4 byte on an index of char a[10] hence a bus error will occur ( am not sure if my understanding is right or wrong )

The problem is not the size of char or int, but their alignment. Generally, architectures are finicky about the addresses you load data/code from, for example you might only be able to load a 16-bit integer from an address that is a multiple of 16-bit or a function must always start on a 4 byte boundary.

If you don't observe this, the processor might read wrong data or punish you with an exception. The OS then could emulate it using multiple aligned accesses or pass it through as a SIGBUS to the user application. The latter being what the author probably experiences on his setup.

How does all this relate to C?

What you have is undefined behavior. The interaction of processor, memory controller, compiler, OS and nasal demonic spawns in your vicinity will all have an effect on what effect this has (if at all). On your computer, it's possible that the processor supports unaligned accesses natively, so it worked, but still it's something you can't rely on: it's simply undefined. (Especially with optimizations, these things can come back to bite you. Works for me™ isn't good enough to write well-defined C code!)

The data you are trying to fetch straddles a 32-bit boundary so 2 memory fetches are required (but the compiler does not necessarily know this at compile time).

Note: The book is very old and is talking about 32 bit CPUs. For 64 bit you may have to change int * p = ( int * ) &(u.a[1]); to int * p = ( int * ) &(u.a[5]); so that all the data required can't be obtained in one memory fetch from an aligned address.

For backward compatibility reasons most Intel CPUs (and derivatives such as AMD) handle memory alignment errors automatically at the instruction level so you would not notice anything wrong (simplistically it automatically adds an extra read having locked the bus to make sure nothing is changed between reads).

On many other CPU architectures (ARM, PowerPC, MIPS, newer Intel architectures) the example code would have caused problems as described but now some operating systems, such as Linux, can be configured to automatically catch the fault and do a "fixup" allowing the program to carry on unaware there was a problem. With most programs this would probably go unnoticed by the user but is quite time consuming and would cause real problems in real time software and drivers.

Time critical code is often conditionally compiled to do or not do unaligned accesses according to the CPU architecture it is being compiled for. In linux the pseudo file "/proc/cpu/alignment" can be used to control kernel behaviour and view statistics about the number of "fixups".

Related