C Strings and Standard Input

Many C tutorials out there will show you some bad ways to do things. I’ll pick on this input and output tutorial as an example.

It has what may appear as a pretty reasonable way to read input with the deprecated gets() function.

#include <stdio.h>
int main( ) {

   char str[100];

   printf( "Enter a value :");
   gets( str );

   printf( "\nYou entered: ");
   puts( str );

   return 0;
}

Or via scanf() like this

#include <stdio.h>
int main( ) {

   char str[100];
   int i;

   printf( "Enter a value :");
   scanf("%s %d", str, &i);

   printf( "\nYou entered: %s %d ", str, i);

   return 0;
}

In either case, the program wants you to enter a string. The string can only fit 100 ascii characters, though should really only have 99 so that your string can end with a 0 byte to be properly NULL-terminated. When I give it 120 a‘s, my system is reasonably displeased as I clobber over other parts of my stack.

$ ./badhabits 
Enter a value :aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1

*** stack smashing detected ***: ./badhabits terminated
You entered: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 1 Aborted (core dumped)

One option would be to use a better format string. Referring to something like the GNU C Library Manual, we can see that the scanf() function has a few other tricks up its sleeve that can help us.

If we really wanted a 99 character limit on this string, we could change the format string to "%99s %d".

$ ./badhabits 
Enter a value :aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

You entered: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 0

In this case scanf() will truncate the string so that it fits into the size of the buffer. If the library is POSIX compliant, the m modifier can also be used to ask scanf() to dynamically allocate your string with malloc() and give you a pointer to that newly allocated memory space that now holds the input string nicely.

#include <stdio.h>

int main() {
  char *name;

  printf("Enter your name: ");
  scanf("%ms", &name);

  printf("Hello %s!\n", name);

  return 0;
}
$ ./betterscanf 
Enter your name: test
Hello test!

Beyond Scanf

Personally, I’m not a big fan of scanf() in general. When hunting for other options, I first will peruse the GNU C Library’s manual. In section 12.9 I find an approach that fits a common need I have, reading one line at a time with getline().

The getline() function offers a few things that I like. It expects to work with dynamically allocated buffers and will allocate or reallocate them to size for you.

ssize_t getline(char **lineptr, size_t *n, FILE *stream);

Using it is pretty simple, it takes a pointer to a char pointer (lineptr) along with a pointer to a size_t type number (n). It’ll read a line from the stream file descriptor and return a ssize_t (signed size) value of the number of bytes read or -1 on failure.

#define _GNU_SOURCE
#include <stdio.h>

int main() {
  char *string = NULL;
  size_t buffer_size = 0;
  ssize_t read_size;

  printf("Enter some stuff!\n");
  read_size = getline(&string, &buffer_size, stdin);

  printf("Read %zd bytes, buffer is %zd bytes\n", read_size, buffer_size);
  printf("Line read:\n%s", string);

  return 0;
}

You need to make sure to set the string to NULL if it’s not already dynamically allocated or you’ll be passing whatever just happened to be laying around in the stack.

Also worth noting, for the environment I’m building this within I needed to place the processor directive #define _GNU_SOURCE prior to including stdio.h to properly pull in the getline() functionality without angering the compiler.

$ ./getline 
Enter some stuff!
weeeeeeeeeeee
Read 14 bytes, buffer is 120 bytes
Line read:
weeeeeeeeeeee

In my run here, the buffer_size is getting set to a larger size than the string, whose length I got back from the getline() call. There is some consideration here on the part of the library that it is more efficient to give a longer buffer since it is likely to be added to later on and resizing buffers can be slow.

Fancier Getline

I like the idea of making a function that abstracts this a bit so it’s a bit friendlier to use. I can reuse portions of the FancyString type I built in a previous post to build some functions that will let me dynamically read a line in a single step.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

typedef struct {
  ssize_t length;
  char *string;
  size_t buffer_size;
} FancyString;


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


FancyString* fancy_getline(FILE *stream) {
  FancyString *new = malloc(sizeof(*new));
  new->string = NULL;
  new->buffer_size = 0;

  new->length = getline(&(new->string), &(new->buffer_size), stream);
  if (new->length == -1) {
    free(new);
    return NULL;
  } else {
    return new;
  }
}


int main() {
  FancyString *line = fancy_getline(stdin);

  printf("Read %zd bytes, buffer is %zd bytes\n",
         line->length,
         line->buffer_size);
  printf("Line read:\n%s", line->string);

  FancyString_free(line);

  return 0;
}
$ ./fancy_getline 
this is a test of the fanciness
Read 32 bytes, buffer is 120 bytes
Line read:
this is a test of the fanciness

I find this a bit more convenient to manage the lines of input, this could even be extended to include a function that would operate like readlines() in Python. I’ll modify my FancyString to support usage as a linked list, I’ll share more about linked lists and other data structure patterns in a future post.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

typedef struct _fancystring {
  ssize_t length;
  char *string;
  size_t buffer_size;
  struct _fancystring *next;
} FancyString;


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


FancyString* fancy_getline(FILE *stream) {
  FancyString *new = malloc(sizeof(*new));
  new->string = NULL;
  new->buffer_size = 0;
  new->next = NULL;

  new->length = getline(&(new->string), &(new->buffer_size), stream);
  if (new->length == -1) {
    free(new);
    return NULL;
  } else {
    return new;
  }
}

FancyString* fancy_readlines(FILE *stream) {
  FancyString *first = NULL;
  FancyString *last = NULL;
  FancyString *i = NULL;

  while ((i = fancy_getline(stream)) != NULL) {
    if (first == NULL) {
      first = i;
      last = i;
    } else {
      last->next = i;
      last = i;
    }
  }

  return first;
}

int main() {
  printf("Enter many lines, end with CTRL+D\n");

  FancyString *line = fancy_readlines(stdin);
  FancyString *previous_line;

  int i = 1;
  while (line != NULL) {
    printf("Line %d: %s", i, line->string);
    i++;
    previous_line = line;
    line = line->next;
    FancyString_free(previous_line);
  }

  return 0;
}
$ ./readlines
Enter many lines, end with CTRL+D
this is a line
and this!
and moar
and moooooaoOOAOOAOARRRRR
Line 1: this is a line
Line 2: and this!
Line 3: and moar
Line 4: and moooooaoOOAOOAOARRRRR

 

Command Line Arguments in C

Today I’m going to share some tips and tricks to using command line arguments with C programs. First I’ll explore the plain ‘ol argc/argv style  followed by a getopt approach.

Let’s jump right to it!

 Classic Approach

Most C programmers will be quite familiar with this approach, so I’ll keep it brief. The most common function signature for main is int main(int argc, char *argv[]). In this setup the argc will tell you the number of arguments passed to the program at launch, with argc getting a list of string pointers to those arguments.

With a small test program, we can inspect these variables pretty easily. I’ll even look at where the argv pointer is and where the string pointers within it are pointing to, because why not!

My classic.c source:

#include <stdio.h>

int main (int argc, char *argv[]) {
  int i;
  printf("Argument Count: %d\n", argc);
  printf("argv is at %p\n", argv);

  for (i = 0; i <= argc; i++) {
    printf("%d at %p: %s\n", i, argv[i], argv[i]);
  }

  return 0;
}

Testing it:

$ ./classic well hello there
Argument Count: 4
argv is at 0x7ffc923396b8
0 at 0x7ffc92339f4d: ./classic
1 at 0x7ffc92339f57: well
2 at 0x7ffc92339f5c: hello
3 at 0x7ffc92339f62: there
4 at (nil): (null)

The first argument is the name of the program as it was executed, the remaining argc - 1 are the command line arguments to that program. They are most often accessed using an index operator [i] as was done here, though usually i < argc; is usually used in the for loop condition to skip the null element that terminates the array.

Since this is a null terminated array, you could also use pointer arithmetic to process through the list and not use argc at all.

#include <stdio.h>

int main (int argc, char *argv[]) {

  char **arg = argv;
  while (*arg) {
    printf("%s\n", *arg);
    arg++;
  }

  return 0;
}
$ ./classic do it live
./classic
do
it
live

How you interpret the command line arguments from there is up to you, though some patterns are very common and there are libraries to assist in implementing them.

Getopt Basics

The getopt() function is part of any Standard C Library implementation that follows a POSIX standard. Many libraries will also follow the GNU standard for getopt_long().

The general idea is that options use a format starting with - followed by a single letter to indicate something about what the user wants the program to do. As an example, many programs on Linux will have a -v option you can use to see more verbose console output, or a -h option to get help on using the program.

The getopt implementation has a few variables that represent the current internal state of its parsing system.

extern int optind, opterr, optopt;

The optind variable is the index value of the next argument that should be handled by the getopt() function. opterr will let you control if the getopt() function should print errors to the console. If the getopt() call returns ? because it did not recognize the option being given, optopt will be set to the character it did not recognize.

#include <stdio.h>
#include <unistd.h>


void print_getopt_state(void) { 
  printf("optind: %d\t" "opterr: %d\t" "optopt: %c (%d)\n" ,
    optind, opterr, optopt, optopt
  );
}


int main (int argc, char *argv[]) {
  print_getopt_state();
  return 0;
}
$ ./getopt 
optind: 1 opterr: 1 optopt: ? (63)

To use the getopt() function you provide the argc and argv variables along with an optstring variable that contains the list of options it should look for.

#include <stdio.h>
#include <unistd.h>


void print_getopt_state(void) {
  printf("optind: %d\t" "opterr: %d\t" "optopt: %c (%d)\n" ,
    optind, opterr, optopt, optopt
  );
}


int main (int argc, char *argv[]) {
  int character;
  char *options = "v";

  print_getopt_state();

  character = getopt(argc, argv, options);

  printf("getopt returned: '%c' (%d)\n", character, character);
  print_getopt_state();

  return 0;
}
$ ./getopt 
optind: 1 opterr: 1 optopt: ? (63)
getopt returned: '�' (-1)
optind: 1 opterr: 1 optopt: (0)

$ ./getopt -v
optind: 1 opterr: 1 optopt: ? (63)
getopt returned: 'v' (118)
optind: 2 opterr: 1 optopt: (0)

$ ./getopt -h
optind: 1 opterr: 1 optopt: ? (63)
./getopt: invalid option -- 'h'
getopt returned: '?' (63)
optind: 2 opterr: 1 optopt: h (104)

On each run of getopt(), until it reaches the end of the argument list and returns -1, it will check the next argument and return the option found or ? if an unrecognized option was given.

#include <stdio.h>
#include <unistd.h>


void print_getopt_state(void) {
  printf("optind: %d\t" "opterr: %d\t" "optopt: %c (%d)\n" ,
    optind, opterr, optopt, optopt
  );
}


int main (int argc, char *argv[]) {
  int character;
  char *options = "abcd";

  print_getopt_state();

  character = getopt(argc, argv, options);

  while(character != -1) {
    printf("getopt returned: '%c' (%d)\n", character, character);
    print_getopt_state();
  
    character = getopt(argc, argv, options);
  }

  printf("getopt returned: '%c' (%d)\n", character, character);
  print_getopt_state();

  return 0;
}
$ ./getopt -d -a -b
optind: 1 opterr: 1 optopt: ? (63)
getopt returned: 'd' (100)
optind: 2 opterr: 1 optopt: (0)
getopt returned: 'a' (97)
optind: 3 opterr: 1 optopt: (0)
getopt returned: 'b' (98)
optind: 4 opterr: 1 optopt: (0)
getopt returned: '�' (-1)
optind: 4 opterr: 1 optopt: (0)

You can also include multiple options in a single argument by just not separating them. Multiple instances of the same option will be iterated on multiple times.

$ ./getopt -baddd
optind: 1 opterr: 1 optopt: ? (63)
getopt returned: 'b' (98)
optind: 1 opterr: 1 optopt: (0)
getopt returned: 'a' (97)
optind: 1 opterr: 1 optopt: (0)
getopt returned: 'd' (100)
optind: 1 opterr: 1 optopt: (0)
getopt returned: 'd' (100)
optind: 1 opterr: 1 optopt: (0)
getopt returned: 'd' (100)
optind: 2 opterr: 1 optopt: (0)
getopt returned: '�' (-1)
optind: 2 opterr: 1 optopt: (0)

Optional and Positional Arguments

A colon after an option in the optstring can be used to indicate that option requires an argument, while two colons can indicate that it supports an argument but is not required. In either case, if an argument is given to an option that supports it, getopt() will set the optarg pointer it provides to the argument.

#include <stdio.h>
#include <unistd.h>


void print_getopt_state(void) {
  printf("optind: %d\t" "opterr: %d\t" "optopt: %c (%d)\t" "optarg: %s\n" ,
    optind, opterr, optopt, optopt, optarg
  );
}


int main (int argc, char *argv[]) {
  int character;
  char *options = "abc:d::";

  print_getopt_state();

  character = getopt(argc, argv, options);

  while(character != -1) {
    printf("getopt returned: '%c' (%d)\n", character, character);
    print_getopt_state();
  
    character = getopt(argc, argv, options);
  }

  printf("getopt returned: '%c' (%d)\n", character, character);
  print_getopt_state();

  return 0;
}
$ ./getopt -dwith -dwithout -c
optind: 1 opterr: 1 optopt: ? (63) optarg: (null)
getopt returned: 'd' (100)
optind: 2 opterr: 1 optopt: (0) optarg: with
getopt returned: 'd' (100)
optind: 3 opterr: 1 optopt: (0) optarg: without
./getopt: option requires an argument -- 'c'
getopt returned: '?' (63)
optind: 4 opterr: 1 optopt: c (99) optarg: (null)
getopt returned: '�' (-1)
optind: 4 opterr: 1 optopt: c (99) optarg: (null)

If optstring begins with -, non-option positional arguments can also be handled. In these cases getopt() will return the value 1 to indicate it has found a positional argument and set the optarg pointer to it.

#include <stdio.h>
#include <unistd.h>


void print_getopt_state(void) {
  printf("optind: %d\t" "opterr: %d\t" "optopt: %c (%d)\t" "optarg: %s\n" ,
    optind, opterr, optopt, optopt, optarg
  );
}


int main (int argc, char *argv[]) {
  int character;
  char *options = "-abc:d::";

  print_getopt_state();

  character = getopt(argc, argv, options);

  while(character != -1) {
    printf("getopt returned: '%c' (%d)\n", character, character);
    print_getopt_state();
  
    character = getopt(argc, argv, options);
  }

  printf("getopt returned: '%c' (%d)\n", character, character);
  print_getopt_state();
  return 0;
}
$ ./getopt well -a -b now
optind: 1 opterr: 1 optopt: ? (63) optarg: (null)
getopt returned: '' (1)
optind: 2 opterr: 1 optopt: (0) optarg: well
getopt returned: 'a' (97)
optind: 3 opterr: 1 optopt: (0) optarg: (null)
getopt returned: 'b' (98)
optind: 4 opterr: 1 optopt: (0) optarg: (null)
getopt returned: '' (1)
optind: 5 opterr: 1 optopt: (0) optarg: now
getopt returned: '�' (-1)
optind: 5 opterr: 1 optopt: (0) optarg: (null)

If optstring begins with a +, it will stop option parsing and return the value -1 at the first non-option argument. The - and + prefixes are only provided by C libraries that follow the GNU extension’s

Long Options

The GNU extensions provide a fancier version of getopt that supports longer, more verbose options that begin with --. It has the same first 3 parameters as getopt(). The 4th parameter is an array, longopts, of struct option structures that describe the longer options. The last parameter is an integer pointer, longindex, that on match will be the index of the matched option from the longopts array. longindex may be set to NULL if you don’t plan to use it.

The struct option has this format:

struct option {
  const char *name;
  int         has_arg;
  int        *flag;
  int         val;
};

The name is the option name that is supported. The has_arg field can be set to no_argument, required_argument or optional_argument, which correlate to the values 0, 1, and 2.

The flag pointer, will be set to val on a match and getopt_long() will return 0. If flag is NULL, val will be what getopt_long()returns when the long option is matched. Often a program will have a long option that returns the short option. This is common for an option like --help, to set val to h so that the code that handles -h can handle either.

Here’s an example that uses a few different ways of handling the long options.

#include <stdio.h>
#include <unistd.h>
#include <getopt.h>


int main (int argc, char *argv[]) {
  int character;
  char *options = "h";
  int longindex;
  int moartest_flag = 0;

  struct option longopts[] = {
    {"help", no_argument, NULL, 'h'},
    {"echo", required_argument, NULL, 0},
    {"longtest", optional_argument, &moartest_flag, 12},
    {NULL, 0, NULL, 0}
  };

  while((character = getopt_long(argc, argv, options, longopts, &longindex)) != -1) {
    printf("getopt_long returned: '%c' (%d)\n", character, character);
    switch (character) {
      case 'h':
        printf("help!\n");
        break;
      case 0:
        printf("longindex: %d\n", longindex);
        printf("longopts[longindex].name = %s\n", longopts[longindex].name);
        printf("optarg: %s\n", optarg);
        printf("moartest_flag: %d\n", moartest_flag);
    }
  }

  return 0;
}
$ ./getopt --help --longtest --echo wee
getopt_long returned: 'h' (104)
help!
getopt_long returned: '' (0)
longindex: 2
longopts[longindex].name = longtest
optarg: (null)
moartest_flag: 12
getopt_long returned: '' (0)
longindex: 1
longopts[longindex].name = echo
optarg: wee
moartest_flag: 12

In this code, -h and --help will match the h case and print my help message. Both --longtest and --echo are handled by the 0 case, I can tell them apart from the value that longindex gets set to. When --longtest is given, the moartest_flag integer will get set to 12 since I provided the pointer to that integer in my flags field for that option.

That covers what I wanted to share on parsing command line arguments! There are other strategies out there for handling it, but these are the most common and I hope this guide proves helpful. If you have any questions or feedback please drop a note in the comments!

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!