As indicated by MikeCAT, the following does not do what you think it is doing... but Why?
char *join_strings(const char *array[])
{
int n_array (sizeof (array) / sizeof (const char *));
You are passing *fullString[]
to your join_strings
function, but what join_strings
gets is effectively **fulString
due to pointer decay. Any time you pass an array to a function (e.g. a[]
) it will decay to a pointer (e.g. *a
). The same is true for an array of pointers.
That is precisely the reason you see the main
function syntax as:
int main (int argc, char *argv[])
or
int main (int argc, char **argv)
In fact, what main receives is the second case, but the syntax allowed is either. So when you pass *fullString[]
to your join_strings
function, pointer decay insures that join_strings
receives **fullString
. That makes the following:
int n_array (sizeof (array) / sizeof (const char *));
effectively int n_array 8/8
or 1
. The sizeof array / sizeof *array
only works in the scope where array
is declared and defined. Once you pass it to another function, pointer decay renders it incorrect.
However, your code is not far off otherwise. There were a few changes to make, but other than this bit of learning that was needed, you were close to a working string concatenation:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
char *join_strings (const char **array, size_t n_array)
{
// int n_array (sizeof (array) / sizeof (const char *));
size_t i;
size_t total_length;
char *joined;
char *end;
size_t *lengths;
// lengths = (int *) malloc (n_array * sizeof (int));
lengths = malloc (n_array * sizeof *lengths);
if (!lengths) {
fprintf (stderr, "Malloc failed.\n");
exit (EXIT_FAILURE);
}
total_length = 0;
for (i = 0; i < n_array; i++) {
lengths[i] = strlen (array[i]);
total_length += lengths[i];
}
joined = malloc (total_length + 1);
if (!joined) {
fprintf (stderr, "Malloc failed.\n");
exit (EXIT_FAILURE);
}
end = joined;
for (i = 0; i < n_array; i++) {
size_t j;
for (j = 0; j < lengths[i]; j++) {
end[j] = array[i][j];
}
end += lengths[i];
}
// end[total_length] = '\0'; /* ALWAYS run valgrind (Note3) */
joined[total_length] = '\0';
// char *result = joined;
free (lengths);
// free (joined);
return joined;
}
int main ()
{
const char *lol0 = "First";
const char *lol1 = "Second";
const char *fullString[] = { lol0, lol1 };
/*const */char *joined = join_strings (fullString, sizeof fullString/sizeof *fullString);
puts (joined);
free (joined);
return 0;
}
Note the change from int
to size_t
in your function for all your length and counter variables. You need to try and match your types to the actual ranges of numbers they will hold. Since there is no negative-length and you are only using positive counters to count up, size_t
will help the compiler warn you when you might be trying to use the variables incorrectly.
Note2: don't cast the return on malloc
. It is nothing more than the address to the start of a memory block. It doesn't have a type. Casting malloc
provides zero benefit, but can mask errors or warnings the compiler would otherwise generate to warn you when you are about to do something dumb.
Note3: If you are allocating memory dynamically Always run your code through valgrind to insure you are using the memory properly. There is no excuse not to, it is dead-bang easy:
valgrind ./myprogname
It not only checks your allocations and frees, but it also check whether you attempt to access memory outside your allocated blocks. (if you compile with symbols enabled -g
valgrind will show you the line numbers associated with the errors) Here valgrind shows:
==27700== Invalid write of size 1
==27700== at 0x40090E: join_strings (ptrdecay.c:45)
==27700== by 0x400960: main (ptrdecay.c:60)
==27700== Address 0x51e00a6 is 10 bytes after a block of size 12 alloc'd
==27700== at 0x4C29964: calloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==27700== by 0x400838: join_strings (ptrdecay.c:29)
==27700== by 0x400960: main (ptrdecay.c:60)
Huh? Look closely at how your were attempting to null-terminate end
. What is the value of end
at that time? Where does end[total_length]
(or equivalently written *(end + total_length)
) put that null-terminator?
==27700== Address 0x51e00a6 is 10 bytes after a block of size 12 alloc'd
There is one other warning you should fix, either by using calloc
for joined
or initializing its values to 0
before filling them. That is all valgrind is complaining about in:
==27647== Conditional jump or move depends on uninitialised value(s)
==27647== at 0x4C2ACF8: strlen (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==27647== by 0x4E9DE2B: puts (in /lib64/libc-2.18.so)
==27647== by 0x40092B: main (ptrdecay.c:62)
The easiest way to eliminate this message is with:
joined = calloc (1, total_length + 1);
Try it and see... When you fix all the issues and run valgrind again, you will see what you want to see everytime:
$ valgrind ./bin/ptrdecay
==27821== Memcheck, a memory error detector
==27821== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==27821== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==27821== Command: ./bin/ptrdecay
==27821==
FirstSecond
==27821==
==27821== HEAP SUMMARY:
==27821== in use at exit: 0 bytes in 0 blocks
==27821== total heap usage: 2 allocs, 2 frees, 28 bytes allocated
==27821==
==27821== All heap blocks were freed -- no leaks are possible
==27821==
==27821== For counts of detected and suppressed errors, rerun with: -v
==27821== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)