views:

323

answers:

10

I have never really done much C but am starting to play around with it. I am writing little snippets like the one below to try to understand the usage and behaviour of key constructs/functions in C. The one below I wrote trying to understand the difference between char* string and char string[] and how then lengths of strings work. Furthermore I wanted to see if sprintf could be used to concatenate two strings and set it into a third string.

What I discovered was that the third string I was using to store the concatenation of the other two had to be set with char string[] syntax or the binary would die with SIGSEGV (Address boundary error). Setting it using the array syntax required a size so I initially started by setting it to the combined size of the other two strings. This seemed to let me perform the concatenation well enough.

Out of curiosity, though, I tried expanding the "concatenated" string to be longer than the size I had allocated. Much to my surprise, it still worked and the string size increased and could be printf'd fine.

My question is: Why does this happen, is it invalid or have risks/drawbacks? Furthermore, why is char str3[length3] valid but char str3[7] causes "SIGABRT (Abort)" when sprintf line tries to execute?

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

void main() {
    char* str1 = "Sup";
    char* str2 = "Dood";

    int length1 = strlen(str1);
    int length2 = strlen(str2);
    int length3 = length1 + length2;

    char str3[length3];
    //char str3[7];

    printf("%s (length %d)\n", str1, length1);           // Sup (length 3)
    printf("%s (length %d)\n", str2, length2);           // Dood (length 4)
    printf("total length: %d\n", length3);              // total length: 7
    printf("str3 length: %d\n", (int)strlen(str3));      // str3 length: 6
    sprintf(str3, "%s<-------------------->%s", str1, str2); 
    printf("%s\n", str3);         // Sup<-------------------->Dood

    printf("str3 length after sprintf: %d\n",     // str3 length after sprintf: 29
      (int)strlen(str3));
}
+9  A: 

This line is wrong:

char str3[length3];

You're not taking the terminating zero into account. It should be:

char str3[length3+1];

You're also trying to get the length of str3, while it hasn't been set yet.

In addition, this line:

sprintf(str3, "%s<-------------------->%s", str1, str2);

will overflow the buffer you allocated for str3. Make sure you allocate enough space to hold the complete string, including the terminating zero.

Philippe Leybaert
+1  A: 

Your str3 is too short - you need to add extra byte for null-terminator and the length of "<-------------------->" string literal.

Out of curiosity, though, I tried expanding the "concatenated" string to be longer than the size I had allocated. Much to my surprise, it still worked and the string size increased and could be printf'd fine.

The behaviour is undefined so it may or may not segfault.

qrdl
+1  A: 

strlen returns the length of the string without the trailing NULL byte (\0, 0x00) but when you create a variable to hold the combined strings you need to add that 1 character.

char str3[length3 + 1];

…and you should be all set.

Bombe
+3  A: 

Instead of trying to learn C by trial and error, I suggest that you go to your local bookshop and buy an "introduction to C programming" book. You'll end up knowing the language a lot better that way.

There is nothing more dangerous than a programmer who half understands C!

Stephen C
I have a couple right next to me :) -- I like experimenting, though -- if I'm curious about how something works, I'll try to find out. Also, while I thank you for recommending the way to learn C, this doesn't really answer the question at all...
bjeanes
+1  A: 

C strings are '\0' terminated and require an extra byte for that, so at least you should do

char str3[length3 + 1]

will do the job.

Vanya
A: 

In sprintf() ypu are writing beyond the space allocated for str3. This may cause any type of undefined behavior (If you are lucky then it will crash). In strlen(), it is just searching for a NULL character from the memory location you specified and it is finding one in 29th location. It can as well be 129 also i.e. it will behave very erratically.

Naveen
+3  A: 

What you have to understand is that C doesn't actually have strings, it has character arrays. Moreover, the character arrays don't have associated length information -- instead, string length is determined by iterating over the characters until a null byte is encountered. This implies, that every char array should be at least strlen + 1 characters in length.

C doesn't perform array bounds checking. This means that the functions you call blindly trust you to have allocated enough space for your strings. When that isn't the case, you may end up writing beyond the bounds of the memory you allocated for your string. For a stack allocated char array, you'll overwrite the values of local variables. For heap-allocated char arrays, you may write beyond the memory area of your application. In either case, the best case is you'll error out immediately, and the worst case is that things appear to be working, but actually aren't.

As for the assignment, you can't write something like this:

char *str;
sprintf(str, ...);

and expect it to work -- str is an uninitialized pointer, so the value is "not defined", which in practice means "garbage". Pointers are memory addresses, so an attempt to write to an uninitialized pointer is an attempt to write to a random memory location. Not a good idea. Instead, what you want to do is something like:

char *str = malloc(sizeof(char) * (string length + 1));

which allocates n+1 characters worth of storage and stores the pointer to that storage in str. Of course, to be safe, you should check whether or not malloc returns null. And when you're done, you need to call free(str).

The reason your code works with the array syntax is because the array, being a local variable, is automatically allocated, so there's actually a free slice of memory there. That's (usually) not the case with an uninitialized pointer.

As for the question of how the size of a string can change, once you understand the bit about null bytes, it becomes obvious: all you need to do to change the size of a string is futz with the null byte. For example:

char str[] = "Foo bar";
str[1] = (char)0; // I'd use the character literal, but this editor won't let me

At this point, the length of the string as reported by strlen will be exactly 1. Or:

char str[] = "Foo bar";
str[7] = '!';

after which strlen will probably crash, because it will keep trying to read more bytes from beyond the array boundary. It might encounter a null byte and then stop (and of course, return the wrong string length), or it might crash.

I've written all of one C program, so expect this answer to be inaccurate and incomplete in a number of ways, which will undoubtedly be pointed out in the comments. ;-)

Rytmis
A: 

A few important points:

  • Just because it works doesn't mean it's safe. Going past the end of a buffer is always unsafe, and even if it works on your computer, it may fail under a different OS, different compiler, or even a second run.
  • I suggest you think of a char array as a container and a string as an object that is stored inside the container. In this case, the container must be 1 character longer than the object it holds, since a "null character" is required to indicate the end of the object. The container is a fixed size, and the object can change size (by moving the null character).
  • The first null character in the array indicates the end of the string. The remainder of the array is unused.
  • You can store different things in a char array (such as a sequence of numbers). It just depends on how you use it. But string function such as printf() or strcat() assume that there is a null-terminated string to be found there.
Artelius
+6  A: 
void main() {
    char* str1 = "Sup"; // a pointer to the statically allocated sequence of characters {'S', 'u', 'p', '\0' }
    char* str2 = "Dood"; // a pointer to the statically allocated sequence of characters {'D', 'o', 'o', 'd', '\0' }

    int length1 = strlen(str1); // the length of str1 without the terminating \0 == 3
    int length2 = strlen(str2); // the length of str2 without the terminating \0 == 4
    int length3 = length1 + length2;

    char str3[length3]; // declare an array of7 characters, uninitialized

So far so good. Now:

printf("str3 length: %d\n", (int)strlen(str3));      // What is the length of str3? str3 is uninitialized!

C is a primitive language. It doesn't have strings. What it does have is arrays and pointers. A string is a convention, not a datatype. By convention, people agree that "an array of chars is a string, and the string ends at the first null character". All the C string functions follow this convention, but it is a convention. It is simply assumed that you follow it, or the string functions will break.

So str3 is not a 7-character string. It is an array of 7 characters. If you pass it to a function which expects a string, then that function will look for a '\0' to find the end of the string. str3 was never initialized, so it contains random garbage. In your case, apparently, there was a '\0' after the 6th character so strlen returns 6, but that's not guaranteed. If it hadn't been there, then it would have read past the end of the array.

sprintf(str3, "%s<-------------------->%s", str1, str2);

And here it goes wrong again. You are trying to copy the string "Sup<-------------------->Dood\0" into an array of 7 characters. That won't fit. Of course the C function doesn't know this, it just copies past the end of the array. Undefined behavior, and will probably crash.

printf("%s\n", str3);  // Sup<-------------------->Dood

And here you try to print the string stored at str3. printf is a string function. It doesn't care (or know) about the size of your array. It is given a string, and, like all other string functions, determines the length of the string by looking for a '\0'.

jalf
A: 

char* str1 = "Sup"; consider this a read only C string

Arabcoder