2

So I'm working through "Sams Teach Yourself C Programming in One Hour a Day, Seventh Edition" Lesson 10 Exercise 7 which asks to "Write a function that accepts two strings. Use the malloc() function to allocate enough memory to hold the two strings after they have been concatenated (linked). Return a pointer to this new string."

I am sure there are much more elegant ways to go about this than what I have attempted below. I am mostly interested in why my solution doesn't work. I have only been learning C for a few months and have no significant programming background. Please let me know why this crashes on compilation. I am using Code Blocks on Win 7 with GNU GCC Compiler if that makes a difference. Thank you :)

#include <stdio.h>
#include <stdlib.h>
char * concatenated(char array1[], char array2[]);
int ctrtotal;

int main(void)
{
    char *comboString;
    char *array1 = "You\'re the man ";
    char *array2 = "Now Dog!";
    comboString = (char *)malloc(ctrtotal * sizeof(char));

    concatenated(array1, array2);

    if (comboString == NULL)
    {
        puts("Memory error");
        exit(1);
    }
    puts(comboString);
    free(comboString);

    return 0;
}

char * concatenated(char array1[], char array2[])
{
    char *array3;
    int ctr;
    int ctr2;

    for (ctr = 0; array1[ctr] != '\0'; ctr++)
        array3[ctr] = array1[ctr];

    ctr2 = ctr;

    for (ctr = 0; array2[ctr] != '\0'; ctr++)
    {
        array3[ctr2 + ctr] = array2[ctr];
    }

    array3[ctr2 + ctr + 1] = '\0';
    ctrtotal = (ctr2 + ctr + 2);

    return array3;
}

Thank you for the help. After reviewing everyone's feedback on my errors I revised the code to the following:

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

char * concatenated(char array1[], char array2[]);

int main(void)
{
    char *array1 = "Testing Testing One Two ";
    char *array2 = "Three.  Finally, not crashing the mem o ry.";
    char *comboString = malloc( (strlen(array1)+strlen(array2) + 1)*sizeof(char));

    comboString = concatenated(array1, array2);

    if (comboString == NULL)
    {
        puts("Memory error");
        exit(1);
    }

    puts(comboString);
    free(comboString);

    return 0;
}

char * concatenated(char array1[], char array2[])
{
    char *array3;
    array3 = malloc( (strlen(array1)+strlen(array2) + 1)*sizeof(char) );

    strcat(array3, array1);
    strcat(array3, array2);

    return array3;
}

If anyone sees any redundancies/unnecessary remaining code the could/should be deleted, please let me know. I recognize the benefit of being as concise as possible.

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
Derekec
  • 25
  • 1
  • 6
  • 1
    `char *array3;` You're using this without ever allocating memory for it. I'm also not confident in the allocation of your `comboString` - aren't you forgetting to allocate an extra slot for the terminating null? – cf- May 13 '14 at 21:38
  • 4
    You also use `ctrtotal` without ever assigning anything to it. – ElGavilan May 13 '14 at 21:39
  • Is it me or `ctrtotal` in never correctly initialized? You just have `int ctrtotal;` so I'm pretty unsure of what `(char *)malloc(ctrtotal * sizeof(char));` will do ... – smagnan May 13 '14 at 21:40
  • @smagnan `ctrtotal` is initialized to zero when the program is loaded, so his code will `malloc` zero bytes. – Jonathon Reinhart May 13 '14 at 21:42
  • @JonathonReinhart thanks (: I wasn't sure of how C was supposed to manage `int`: initialisation to 0 or old value still in the memory (and then reading garbage) – smagnan May 13 '14 at 21:49
  • 1
    @smagnan See http://stackoverflow.com/questions/1597405 – Jonathon Reinhart May 13 '14 at 21:49

4 Answers4

4

Your code has a bunch of issues:

  • int ctrtotal is never initialized, so you are mallocing 0 bytes
  • concatenated() is copying characters to an uninitialized array3. This pointer should point to a mallocd buffer.
  • If concatenated is allocating the memory, then main doesn't need to. Instead it should use the result of concatenated.

I don't want to give you the full code, and let you to miss out on this learning opportunity. So concatenated should look like this, in psuedo-code:

count = length_of(string1) + length_of(string2) + 1
buffer = malloc(count)
copy string1 to buffer
copy string2 to buffer, after string1
set the last byte of buffer to '\0' (NUL)
return buffer

In C, strings are represented as a NUL-terminated array of characters. That's why we allocate one additional byte, and terminate it with \0.


As a side-note, when dealing with strings, it is far easier to work with pointers, instead of treating them as arrays and accessing them via indices.

There's a lot of code here that just doesn't make any sense. I suggest that you first write this program on paper. Then, "execute" the program in your head, stepping through every line. If you get to something you don't understand, then you need to either fix your understanding, or your incorrect code. Don't try to write code that looks like some other bit of code.


There's also a library function called strcat which will make this task even easier. See if you can figure out how to use it here.

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

                                                                                                    char *concatenate2(const char* s1, const char* s2);

                                                                                                    int main(void)
                                                                                                    {
                                                                                                        char *comboString;
                                                                                                        char *array1 = "You're the man ";
                                                                                                        char *array2 = "Now Dog!";

                                                                                                        comboString = concatenate2(array1, array2);

                                                                                                        if (comboString == NULL)
                                                                                                        {
                                                                                                            puts("Memory error");
                                                                                                            exit(1);
                                                                                                        }
                                                                                                        puts(comboString);
                                                                                                        free(comboString);

                                                                                                        return 0;
                                                                                                    }

                                                                                                    char *concatenate2(const char* s1, const char* s2)
                                                                                                    {
                                                                                                        char *result;

                                                                                                        result = malloc(strlen(s1) + strlen(s2) + 1);

                                                                                                        *result = '\0';

                                                                                                        strcat(result, s1);
                                                                                                        strcat(result, s2);

                                                                                                        return result;
                                                                                                    }
Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
  • 1
    Wouldn't `strlen(string1) + strlen(string2)` still be one character too short? [`strlen` doesn't seem to count the trailing null](http://www.cplusplus.com/reference/cstring/strlen/), meaning there's just enough room allocated for the contents of `string1` + the contents of `string2` but not enough for a trailing null. – cf- May 13 '14 at 21:44
  • The calling code needs to capture the return from `concatenated()` and use that, too. – Jonathan Leffler May 13 '14 at 21:45
  • Thanks for the feedback everyone. It's amazing how active these forums are. This is all really helpful. – Derekec May 13 '14 at 21:56
  • ``[Stack Overflow is not a forum](http://meta.stackexchange.com/questions/92107/).`` – Jonathon Reinhart May 13 '14 at 22:10
  • @Derekec Not a problem, I was just pointing out that link, to show how SO is different (and more useful!) – Jonathon Reinhart May 14 '14 at 02:18
1

You forgot to allocate memory for third, concatenated, array of chars (in function) You should do something like this:

char *array3;
array3 = (char *)malloc( (strlen(array1)+strlen(array2) + 1)*sizeof(char) ); // +1 for '\0' character.

and then write chars from first and second array into third.

elwin013
  • 519
  • 10
  • 19
1

Perhaps a stroll through the question code is best.

#include <stdio.h>
#include <stdlib.h>
char * concatenated(char array1[], char array2[]);
int ctrtotal;

Notice that the above line declares ctrtotal to be an integer, but does not specify the value of the integer.

int main(void)
{
   char *comboString;

   char *array1 = "You\'re the man ";
   char *array2 = "Now Dog!";
   comboString = (char *)malloc(ctrtotal * sizeof(char));

Notice that the above line allocates memory and sets 'comboString' to point at that memory. However, how much memory is being allocated?

(ctrtotal[???] * sizeof(char)[1])

What is the value of (??? * 1) ? This is a problem.

   concatenated(array1, array2);

The intent of the line above is that array1["You\'re the man "] and array2["Now Dog!"] will be joined to form a new string["You\'re the man Now Dog!"], which will be placed in allocated memory and returned to the caller.

Unfortunately, the returned memory containing the string is not captured here. For example, perhaps the above line should be:

   comboString = concatenated(array1, array2);

While this make sense, for this line, it begs a question of the purpose of the lines:

   comboString = (char *)malloc(ctrtotal * sizeof(char));

as well as the global variable:

   int ctrtotal;

and the later reference:

   ctrtotal = (ctr2 + ctr + 2);

Perhaps all of these 3 lines should be deleted?

   if (comboString == NULL)
   {
      puts("Memory error");
      exit(1);
   }

   puts(comboString);
   free(comboString);

   return 0;
}

 char * concatenated(char array1[], char array2[])
   {
   char *array3;

Notice that '*array3' is now a defined pointer, but it is not pointing anywhere specific.

   int ctr;
   int ctr2;

The purpose of 'concatenated()' is to join array1 and array1 into allocated array3. Unfortunately, no memory is allocated to array3.

Below, the memory where array3 is pointing will be modified. Since array3 is not pointing anywhere specific, this is not safe.

Prior to modifying memory where array 3 is pointing, it is important to point array3 at memory where it is safe to modify bytes. I suggest that the following code be inserted here:

   array3 = malloc(strlen(array1) + strlen(array2) + 1);

Now, array3 points to allocated memory, large enough to hold both strings plus the string termination character '\0'.

   for (ctr = 0; array1[ctr] != '\0'; ctr++)
      array3[ctr] = array1[ctr];

   ctr2 = ctr;

   for (ctr = 0; array2[ctr] != '\0'; ctr++)
   {
      array3[ctr2 + ctr] = array2[ctr];
   }

   array3[ctr2 + ctr + 1] = '\0';
   ctrtotal = (ctr2 + ctr + 2);

   return array3;
   }
Mahonri Moriancumer
  • 5,993
  • 2
  • 18
  • 28
  • `"(One may assume (incorrectly) that the value of ctrtotal is zero. The actual value of ctrtotal is unknown)."` This is incorrect. See [this question](http://stackoverflow.com/questions/1597405). Globals are initialized to zero. Local (aka stack) variables however, are not. – Jonathon Reinhart May 13 '14 at 22:09
  • @JonathonReinhart, you are absolutely correct. For some reason my mind had placed the variable inside of main(). After all, it made no sense to me that it be global. I have fixed my answer. Thank you. – Mahonri Moriancumer May 13 '14 at 22:13
0

I am responding to your revised code. There are a few bugs in it.

...
char *array2 = "Three.  Finally, not crashing the mem o ry.";
char *comboString = malloc( (strlen(array1)+strlen(array2) + 1)*sizeof(char));

comboString = concatenated(array1, array2);
...

The malloc is unnecessary here and actually a bug in your code. You are allocating a block of memory, but you then replace the value of the pointer comboString with the pointer from the call to concatenated. You lose the pointer to the block of memory allocated in main and thus never are able to free it. Although this will not be a problem in the code you have right now since main returns soon after, it could cause a memory leak in an application that ran for a longer time.

strcat(array3, array1);

This is also a bug. strcat is going to walk through array3 to find '\0' and then once it is found copy in array1 from that index on, replacing the '\0'. This works fine here since the memory block that was allocated for array3 is going to be zeroed out** as no block has yet been freed by your program. However, in a longer running program you can end up with a block that does not start with a '\0'. You might end up corrupting your heap, getting a segfault, etc.

To fix this, you should use strcpy instead, array3[0] = '\0', or *array3 = '\0'

** When the operating system starts your program it will initialize the memory segment it reserves for it with zeroes (this actually isn't a necessity but will be true on almost any operating system). As your program allocates and frees memory, you will eventually wind up with values that are not zero. Note that the same bug can occur with uninitialized local variables such as:

int i;
for (; i < 10; i++);

This loop will run 10 times whenever the space on the runtime stack where i is stored is already 0.

Overall, the takeaway is to be very careful with arrays and dynamic memory allocation in C. C offers you none of the protections that modern languages do. You are responsible for making sure you stay within the bounds of your array, initialize your variables, and properly allocate and free your memory. Neglecting these things will lead to obscure bugs that will take you hours to find, and most of the times these bugs will not appear right away.

Joel Dentici
  • 206
  • 2
  • 5