C strcat, strcpy and armageddon, part 4

See also: part 1part 2 and part 3.

The story so far . . . String copies can be made much safer by using strncpy() instead of strcpy(). strncpy() will take up slightly more code space than strcpy() and may be slower.

Now let’s move on to appending strings with strcat() (“string concatenate”). From the very-useful cplusplus.com website:

  • strcat( char *destination, const char *source ) – Concatenate strings. Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination.

As an example of normal use, perhaps you want to take two string buffers that contain a first and last name, and put them together and make a full name string buffer:

char firstName[40];
char lastName[40];
char fullName[81]; // firstName + space + lastName

// Load first name.
strcpy( firstName, "Zaphod" );

// Load last name.
strcpy( lastName, "Beeblebrox" );

// Create full name by starting with first name...
strcpy( fullName, firstName );

// ...then appending a space...
strcat( fullName, " " );

// ...then appending the last name.
strcat( fullName, lastName );

The fullName buffer looks like this:

// strcpy( fullName, firstName )
+---+---+---+---+---+---+---+---+---+---+---+---+-...
| Z | a | p | h | o | d | 0 |   |   |   |   |   | ...
+---+---+---+---+---+---+---+---+---+---+---+---+-...

// strcat( fullName, " " );
+---+---+---+---+---+---+---+---+---+---+---+---+-...
| Z | a | p | h | o | d |   | 0 |   |   |   |   | ...
+---+---+---+---+---+---+---+---+---+---+---+---+-...

// strcat( fullName, lastName )
+---+---+---+---+---+---+---+---+---+---+---+---+-...
| Z | a | p | h | o | d |   | B | e | e | b | l | ...
+---+---+---+---+---+---+---+---+---+---+---+---+-...

Hopefully you get the idea.

In this simple example, we are controlling the length of the name strings being copied in. We know that fullName can hold 81 bytes, so we know firstName and lastName plus the space in between must be less than 81 bytes long to avoid overflowing the fullName buffer.

In a perfect world, that is fine.  But in a perfect world, we would never need any error checking. Let’s just pretend the world isn’t perfect and do this the safe(r) way.

Just like strcpy() has strncpy(), strcat() also has a safer version. It is called strncat():

  • char * strncat ( char * destination, const char * source, size_t num ) – Append characters from string. Appends the first num characters of source to destination, plus a terminating null-character.
    If the length of the C string in source is less than num, only the content up to the terminating null-character is copied.

For strncat(), num specifies how many characters of the source to append to the destination buffer. If you know the destination buffer (fullName, in this example) is 81 characters (because it is, in this example), you might think you could just do this:

strncat( fullName, firstName, 81 );

While you can do that, that would be wrong. The num count only controls how many characters are appended — it does not have anything to do with the count of how many characters are already in the destination buffer. For example, say fullName already has a 6 character firstName copied to it:

+---+---+---+---+---+---+---+---+--...--+---+---+---+
| Z | a | p | h | o | d | 0 |   |  ...  |   |   |   | <- fullName
+---+---+---+---+---+---+---+---+--...--+---+---+---+

If fullname is able to hold 81 characters, and already contains “Zaphod” (6 characters, not counting the null), the maximum size of a string we could append would be 75 (81-6) characters. Remember that the null (0) character just marks the end of a C string, and gets overwritten by the next strcat() data.

This means what we really want is:

strncat( fullName, firstName, 75 );

Or do we? Actually, there’s another difference with strncat() versus strncpy(), and it was clearly stated in the function description:

Appends the first num characters of source to destination, plus a terminating null-character.

strncat() always appends the null (0) character, meaning if you gave it a long source string and told it to append up to 10 characters, it would append 10 characters plus the null (0) character. Thus, that 75 could actually append 76 characters! We have to always subtract one to avoid a buffer overrun.

// Copy up to 74 characters + null to the 81 character
// fullName buffer which already contains 6 characters.
strncat( fullName, firstName, 74 );

But hard-coding the numbers like that isn’t possible if we don’t know how many characters are already in the destination buffer. Instead, we can use some other C string calls to determine that and then do some math.

strlen() will return a count of how many characters are in a string buffer. It counts up to the first null (0) it finds. Thus, strlen(fullName) would return 6 (“Zaphod”). If we know the full size of the buffer, we can use strlen() to determine what is already there, and then simply subtract to know how many free bytes the buffer has. And since strncat() always adds a null, we subtract 1:

strncat( fullName, firstName, 81-strlen(fullName)-1 );

This is getting messy, but that is the basic way to ensure that strcat() doesn’t append too much data. Let’s update the original example a bit more:

#define FIRSTNAME_SIZE 40
#define LASTNAME_SIZE  40
// firstName + space + lastName
#define FULLANME_SIZE  FIRSTNAME_SIZE + 1 + LASTNAME_SIZE

char firstName[FIRSTNAME_SIZE];
char lastName[LASTNAME_SIZE];
char fullName[FULLNAME_SIZE]; 

// Load first name.
strncpy( firstName, "Zaphod", FIRSTNAME_SIZE-1 );
firstName[FIRSTNAME_SIZE-1] = '\0';

// Load last name.
strncpy( lastName, "Beeblebrox", LASTNAME_SIZE-1 );
lastName[LASTNAME_SIZE-1] = '\0';

// Create full name by starting with first name...
strncpy( fullName, firstName, FULLNAME_SIZE-1 );
fullName[FULLNAME_SIZE-1] = '\0';

// ...then appending a space...
strncat( fullName, " ", FULLNAME_SIZE-strlen(fullname)-1 );

// ...then appending the last name.
strncat( fullName, lastName, FULLNAME_SIZE-strlen(fullName)-1 );

So much extra code, but necessary to avoid a potential buffer overrun if the string being appended was passed in from another function where you don’t know the length.

Now we have a “safe” string append that can’t possibly write past the end of the destination buffer (fullName). If the string is too long, it just stops and puts a null there, truncating the string.

If every string copy is done using strncpy() to assure the destination buffer is never overran, and if every string append is done using strncat() with checks to limit how many characters can be appended, you practically eliminate the chance that a buffer overrun could occur and corrupt or crash your program.

However… If I were writing code to run a nuclear reactor, I might still take some extra steps to make sure the data I am using is valid.

Next time, we will look at a problem with this code. (Hint: What if something is wrong with the initial buffer that you are trying to append to?)

Until then…

4 thoughts on “C strcat, strcpy and armageddon, part 4

    1. Allen Huffman Post author

      I was about to give an excited response on you getting the reference! Until I realized you were pointing out an embarrassing typo. I shall go fix that and the world will never know it happened. THanks :)

      Reply
  1. Pingback: C strcat, strcpy and armageddon, part 6 | Sub-Etha Software

Leave a Reply