Tuesday, February 15, 2011

Understanding quirks of C: Structures

Targeted audience
C programmers

System information
Linux ubuntu 2.6.32-25-generic #44-Ubuntu SMP i686 GNU/Linux
gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)

Structure are the most popular user-defined data type in C. It allows an user to create new data type by packing different data types and use them with a single name. In this article we will discuss fundamental operations provided by C structures and their internal behavior. We'd use following structure for understanding the concepts.


struct test{
  char c;
  int a;
  char d;


A variable of type “structure test” would have following memory layout in the process stack. We are assuming that compiler padding is done for a 4-byte boundary.

 Structure layout in memory

Let's assign values to this structure variable.


struct test var = {'a', 10, 'b'};
struct test *ptr;

ptr = &var;

printf(“%c”, var.d);
printf(“%c”, *(ptr->d));


How do the member access expression “var.d” and “ptr->d” work?

“var.i”: This expression is converted by the compiler into two components:
          a) Base address of symbol 'var' and,
          b) Offset of symbol 'd' in the structure memory layout

Base address of the structure variable is “0x0000” in our example. Next, compiler finds the distance of 'd' from the base. The distance is 8 bytes (1+3+4).

Similarly, “ptr->d” is also factored in mentioned manner, and address of member 'd' is returned.

How to calculate a member's byte offset in a given structure

In a trivial manner, we can calculate offset of member 'd' by getting the address of first element of the structure i.e. 'c' and then finding the difference with address of 'd'.


offset_of_d = (&var.d - &var.c);


The difference of these two addresses would fetch us the offset of 'd'. “&(var.c)” fetches us the base address of the structure.

The statement &(var.d) would be treated as = (Base address of variable 'var' + distance of 'd' from the base).

Efficient method to find the offset of a structure member
If we could set the base address to zero in above equation, we would directly get the offset of 'd' i.e. 8.

We can accomplish this as follows:


#define POINTER ((struct test*)(0))

int main()
  unsigned int x;
  x = &(POINTER->d);
  printf("\nOffset of d = %d\n", x);


The macro “POINTER” expands to “&(((struct test*) (0))->d)”.

We are typecasting '0' as an address, pointing to type of data 'struct test'. Now when you do “&(((struct test*)(0))->d)”, it is fetching the address of 'd' as:
                      (Base address + offset of d in the structure).

Since base address is set to zero, we are left with the offset of 'd'. We are tricking the compiler by giving base address as zero. This trick will work irrespective of compiler's padding scheme.

Both the discussed approaches lack portability though, and hence may suffer with incorrect output on different hardware. Another portable way to accomplish the same is with the help of “offsetof” macro, defined in header file stddef.h. The macro 'offsetof' accepts two arguments:
The structure definition, so you don't need to create a structure variable
The member element, for which the offset is to be calculated.

This macro too takes care of structure padding of elements, performed by the compiler.


#include "stddef.h"
int main()
  unsigned int x = offsetof(struct test, d);
  printf("\nOffset of d = %d\n", x);


How “sizeof” behave in a quaint manner, where syntactically you find a NULL pointer dereference?

Consider the following code:


struct test{
  char c;
  int a;
  char d;

int main()
  // Define a dangling pointer of type struct test
  struct test *p;

  // Getting the size of the structure with an uninitialized pointer
  printf("%u", sizeof(*p));
  printf("%u", sizeof(p));

  // Try it with one of implicit types like 'char'
  char *i= 'a';
  printf("%u", sizeof(*i)); // Displays 1, as the size needed to store a character


If you dereference a pointer inside “sizeof” operator, it fetches the size of data-type, the pointer is pointing to, i.e. 12 bytes for “struct test”.

 It is because sizeof operator works on “data type”, not data. And interestingly, it works for both internal types and language defined types. So, when you dereference a pointer inside sizeof; you are asking for the number of bytes pointee would need in the memory.

char *c;

// It would fetch you result as 1 byte.


Remember that it is illegal to dereference a dangling pointer otherwise. In the mentioned code, you do not dereference the address carried by the pointer, and hence never get a classic segmentation fault error.

Hope you enjoyed these interesting facts of C. Please share your comments and suggestions.


Tuesday, February 1, 2011

How to debug with GDB: Philosophy

Debugging is an art.

In general, debugging is the most admired tool in the developer's arsenal. My points in this article are related to GNU GDB debugger, for Linux operating system.

What is your first reaction when you see a core-dump?

Let's debug it!

Hold-on. Wait, and first ruminate over the problem. I'd suggest performing a few rituals before treading on to debugging would be helpful in saving time and effort.

o) Try to reproduce the problem. Ensure problem is consistent.

o) Check for the logs emitted by the application. If not available, see if you could get the logs.

o) Note down the application configuration, including the input, system configuration and every tiny detail that makes sense to you.

o) Okay, check the stack-frames with "backtrace".

o) You may inquire about frame locals and dig-in parent frames as well.

o) Also, it's useful to have a look at the code and a 2-3 level dry-run of code to reach at the point of failure. So, say you have a stack-trace as:
top() --> dumps core

Check how did we reach till top from say bar(). Look out for any ancillary function call in meanwhile and report its side-effect.

Remember, debugging is more than digging-deep; it's understanding the code tree and it's branches. Always approach to the problem on a high level, think what might have caused this failure.

Then, just jump in.