Structures and Code Organization in C

Many C tutorials very briefly gloss over structures and most don’t offer any guidance on how to organize your code and build it in a more modular fashion.

In this post I will share the general system that I follow and prefer when it comes to organizing my C code. There are many other perfectly valid ways to organize your code; this is what works well for me and hopefully for you too!



Code Organization

For this post, I’m going to define a new type, FancyString, along with a few functions that can provide some useful operations with it.  I’ll have a main.c file to hold the application code I’m using to play with it, and main.h to include my headers for it. All of the declarations and data needed to use FancyString will be in fancystring.h, with the implementations of the functions declared in fancystring.c.

As I normally do, I’ll start by building a Makefile.

PROGRAM = test
COMPILE_PROGRAM = gcc -Wall -o $@ $<
COMPILE_OBJECT = gcc -Wall -c -o $@ $<


all: $(PROGRAM)

$(PROGRAM): main.c main.h fancystring.o
  $(COMPILE_PROGRAM) fancystring.o

fancystring.o: fancystring.c fancystring.h
  $(COMPILE_OBJECT)

clean:
  @rm -vf $(PROGRAM) fancystring.o

This time around, I’m using the -c flag for gcc to have it compile some the code to an object file fancystring.o, and not link it into a program or library. When I compile the actual program I’ll include this object file as a source, and it’ll end up in my test program. This will let me skip recompiling my fancystring code if I don’t make any changes to it.

For main.c, I’ll start with a basic shell of a program.

#include "main.h"

int main(int argc, char *argv[]) {
  return 0;
}

For the accompanying header, main.h, it’ll look pretty bare. I’ll just include my fancystring.h header.

#include "fancystring.h"

Now for fancystring.c, I don’t have anything quite yet. I’ll get to that file when I start implementing some code that uses my new structure. For fancystring.h, I’ll have a few standard library includes but eventually my structure will be defined here along with declarations of functions that will work with it.

In larger projects you might have multiple files that reference this header, and if it’s included twice the compiler will lose it’s cool because you defined some things multiple times. To prevent this, I’ll wrap the whole header in a preprocessor ifndef (if not defined) / endif combination. When the first preprocessor line sees that my macro __FANCYSTRING_H__ is not defined the next line of the will define that macro so later it can be checked against to make sure the contents of the header will only be handled one time, no matter how many bits of code are including it.

#ifndef __FANCYSTRING_H__
#define __FANCYSTRING_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#endif

That’ll give the general framework to build the rest of the code within. Anything I don’t want duplicated during compile will go between my ifndef and its endif.

Building the FancyString Type

I’ll start off by declaring my structure for the FancyString type.

struct FancyString {
  unsigned int length;
  char *string;
};

This structure has 2 fields: length contains the length of the string that the string pointer points to.

When you define a structure like this, all your variables of this structure type would have to be defined as struct FancyString variableName;.  To make it a bit shorter we can declare this structure as a data type instead with typedef.

The general syntax of typedef is typedef <declaration or existing type> <new type name>;. You could use this to create a type named byte that is a shortcut to an unsigned char with typedef unsigned char byte;. In this case I’ll declare my structure and type all in one go within my fancystring.h header.

#ifndef __FANCYSTRING_H__
#define __FANCYSTRING_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  unsigned int length;
  char *string;
} FancyString;

#endif

With my structure in place, I’ll whip up a few functions that will help me dynamically allocate an empty FancyString object and free it from memory. I’ll add the declarations of these functions to my fancystring.h header so that other files can be aware of it’s existence.

#ifndef __FANCYSTRING_H__
#define __FANCYSTRING_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
  unsigned int length;
  char *string;
} FancyString;

FancyString* FancyString_new();
void FancyString_free(FancyString *target);

#endif

The actual implementation of these will be in fancystring.c.

#include "fancystring.h"

FancyString* FancyString_new() {
  FancyString *new = malloc(sizeof(*new));
  new->length = 0;
  new->string = NULL;

  return new;
}


void FancyString_free(FancyString *target) {
  if (target->string) {
    free(target->string);
  }
  free(target);
}

The first function here dynamically allocates memory for the structure with malloc(), and sets the other variables to useful defaults.

Structure fields can be accessed in two ways. If you’re dealing with a direct instance of a structure, rather than a pointer to the structure, you use the . operator to access the field. For this structure I’m always going to use it as a pointer, so I use the -> operator instead.

I could still use . operator with a pointer if i really wanted to by first dereferencing the pointer with something like (*new).length = 0; but that is pretty wonky so I won’t be doing that.



Building Around the Structure

This string is not yet fancy at all, but there’s a few things I’ve decided for it, firstly that it’s only used for dynamically allocated strings. Though it’ll still be pretty nice to define it with normal static strings in C. I’ll add another function that’ll let me create a new FancyString from an ordinary C string.

FancyString* FancyString_new_from_string(char *string) {
  FancyString *new = FancyString_new();

  new->length = strlen(string);
  new->string = malloc(new->length + 1);
  strcpy(new->string, string);

  return new;
}

Now I’ll give it a test in my main function.

#include "main.h"

int main(int argc, char *argv[]) {
  FancyString *test = FancyString_new_from_string("Well hello!");

  printf("%s\n", test->string);

  FancyString_free(test);
  return 0;
}

It works like a charm! Though it’s still not particularly useful. As a final exercise, and to give it some purpose in life, I’ll create another function that let’s me concatenate other C strings to the end of it. Unlike normal strcat(), the FancyString concatenation will automatically resize my string so that it won’t risk a buffer overflow.

void FancyString_concat(FancyString *target, char *string) {
  if(!target->string) {
    target->length = strlen(string);
    target->string = malloc(target->length + 1);
    strcpy(target->string, string);
  } else {
    target->length += strlen(string);
    target->string = realloc(target->string, target->length + 1);
    strcat(target->string, string);
  }
}

This new function will do the initial allocation if the FancyString was initially empty, otherwise it’ll use realloc() to expand the dynamically allocated buffer and then concatenate it all together.

I’ll give this all one more test in main:

#include "main.h"

int main(int argc, char *argv[]) {
  FancyString *test = FancyString_new();

  printf("new: %s\n", test->string);

  FancyString_concat(test, "hello");
  printf("first concat: %s\n", test->string);

  FancyString_concat(test, ", world");
  printf("second concat: %s\n", test->string);

  FancyString_free(test);
  return 0;
}
$ make
gcc -Wall -o test main.c fancystring.o
$ ./test 
new: (null)
first concat: hello
second concat: hello, world

Works as expected!

That’ll wrap things up for this post, if you have any questions or feedback please drop a note in the comments!

Leave a Reply

Your email address will not be published. Required fields are marked *