0

My code works fine, but I am receiving valgrind errors. I want to know how to correct my code to in respect to using these malloc and free statements correctly with the char * * dest. Please don't tell me not to malloc and free unless I am doing it at the incorrect locations. Having either or both the corrected code for strcat_ex in answer03.c or an explanation of my misunderstanding of malloc, free, and initialization after malloc would be greatly appreciated. I apologize in advance for the long post, but I wanted to provide everything necessary.

More information: I am mainly focusing on the method strcat_ex (this is not the same as strncat -- read the description of the function to see the difference with int *n). The problem occurs with the fact that I need to remalloc the parameter memory for the string (char *) in dest (char **) and if it doesn't have enough space allocated with it and after I malloc it is not initialized. This doesn't make sense to me how to initialize "heap" memory after a malloc. I didn't believe initialization had to happen after a malloc.

Note: pa03.c and answer03.h should not be changed at all.

Here is the relevant valgrind error (memcheck.log):

==28717== 1 errors in context 7 of 10:
==28717== Conditional jump or move depends on uninitialised value(s)
==28717==    at 0x402D09C: strcat (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==28717==    by 0x8048F98: strcat_ex (answer03.c:29)
==28717==    by 0x8048631: main (pa03.c:16)
==28717==  Uninitialised value was created by a heap allocation
==28717==    at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==28717==    by 0x8048F46: strcat_ex (answer03.c:21)
==28717==    by 0x8048631: main (pa03.c:16)
==28717== 
==28717== ERROR SUMMARY: 10 errors from 10 contexts (suppressed: 0 from 0)

Lines referenced:

Line 16 (from pa03.c) SHOULD NOT BE CHANGE. Serves as an example of a call to the method parameters def, n, src, and return variable result are declared below in pa03.c:

result=strcat_ex(&dest, &n, src);

Line 21 (from answer03.c):

char * buffer = malloc(1 + 2 * (sizeOfDest + strlen(src)));

Line 29 (from answer03.c):

buffer = strcat(buffer,src);

Here is the relevant source code. This is where the valgrind error is and stackoverflow knowledge is needed (answer03.c):

Edit: Comments have been added and lines have been commented out to remove an error of mine in the code that had nothing to do directly with my question. I apologize for these heinous mistakes, but left the lines in there to help future readers understand.

#include "answer03.h"
#include <string.h>

char * strcat_ex(char * * dest, int * n, const char * src)
{
            //Edit: Removed Line Below - Irrelevant variable resplaced with *n
    //int sizeOfDest;

    if(*dest == NULL)
    {
        *n = 0;
    }
    else
    {
        //Edit: Removed Line Below - variable replaced with *n 
        //sizeOfDest = strlen(*dest);
    }
    //Edit: Removed Line Below
    //if(*dest != NULL && sizeOfDest >= 1 + sizeOfDest + strlen(src))
    //Edit: Corrected Line
    if(*dest !=NULL && *n >= 1 + strlen(*dest) + strlen(src)) 
    {
        strcat(*dest, src);
    }
    else
    {
        //Edit: *n replaced sizeOfDest and changes needed to be made to reflect this. Commented out lines were incorrect and irrelevant. Lines directly below them are the corrected versions, until you reach the next blank line

        //*n = 1 + 2 * (sizeOfDest + strlen(src));
        if(*dest != NULL)
           *n = 1 + 2 * (strlen(*dest) + strlen(src));
        else
           *n = 1 + 2 * strlen(src); 

        //char * buffer = malloc(1 + 2 * (sizeOfDest + strlen(src)));
        char * buffer = malloc(sizeof(char) * *n);

        if(*dest != NULL)
        {
            strcpy(buffer, *dest);
            free(*dest);
        }
        *dest = malloc(sizeof(buffer));
        buffer = strcat(buffer,src);
        *dest = buffer;
    }
    return *dest;
}




EVERYTHING BELOW THIS POINT SHOULD REMAIN UNCHANGED AND IS KNOWN TO BE CORRECT:

My compile statement (Makefile):

gcc -Wall -Wshadow -g pa03.c answer03.c -o pa03

My valgrind statement (Makefile):

valgrind --tool=memcheck --leak-check=full --verbose --track-origins=yes --log-file=memcheck.log ./pa03

Here is the function definition for strcat_ex (answer03.h):

#ifndef PA03_H
#define PA03_H 

#include <stdlib.h>

/**
 * Append the C-string 'src' to the end of the C-string '*dest'.
 *
 * strcat_ex(...) will append the C-string 'src' to the end of the string
 * at '*dest'. The parameter 'n' is the address of a int that specifies how
 * many characters can safely be stored in '*dest'. 
 *
 * If '*dest' is NULL, or if '*dest' is not large enough to contain the result
 * (that is, the sum of the lengths of *dest, src, and the null byte), then
 * strcat_ex will:
 * (1) malloc a new buffer of size 1 + 2 * (strlen(*dest) + strlen(src))
 * (2) set '*n' to the size of the new buffer
 * (3) copy '*dest' into the beginning of the new buffer
 * (4) free the memory '*dest', and then set '*dest' to point to the new buffer
 * (5) concatenate 'src' onto the end of '*dest'.
 *
 * Always returns *dest.
 *
 * Why do we need to pass dest as char * *, and n as int *? 
 * Please see the FAQ for an answer.
 *
 * Hint: These <string.h> functions will help: strcat, strcpy, strlen.
 * Hint: Leak no memory.
 */
char * strcat_ex(char * * dest, int * n, const char * src);
//...

Here is the relevant code that calls source as a test (pa03.c):

#include <stdio.h>
#include <string.h>
#include "answer03.h"

int main(int argc, char **argv)
{
  char * src;
  char * dest;
  char * result;
  int n;

  src="World!";
  dest=NULL;
  result=strcat_ex(&dest, &n, src);
  printf("src=\"World!\";\ndest=NULL;\nstrcat_ex(&dest, &n, src);\n --> gives %s with n=%d\n",result,n);
  result=strcat_ex(&dest, &n, "");
  printf("Then strcat_ex(&dest, &n, \"\") yields --> gives %s with n=%d\n",result,n);
  strcpy(dest,"abc");
  result=strcat_ex(&dest, &n, "def");
  printf("Then strcpy(dest,\"abc\"); strcat_ex(&dest, &n, \"def\") yields --> gives %s with n=%d\n",result,n);  
  free(dest);
  //...

Here is the relevant output (print statments from pa03.c): Note this is correct output (which my current code is able to produce).

src="World!";
dest=NULL;
strcat_ex(&dest, &n, src);
 --> gives World! with n=13
Then strcat_ex(&dest, &n, "") yields --> gives World! with n=13
Then strcpy(dest,"abc"); strcat_ex(&dest, &n, "def") yields --> gives abcdef with n=13
//...

Last words:

I have attached the files needed to compile this code as well as the valgrind error log in linux using gcc and valgrind. There is more in the valgrind, but I posted what I believe to be most relevant. Thanks in advance.

Zip including all files:
http://www.filedropper.com/files_11

napkinsterror
  • 1,915
  • 4
  • 18
  • 27
  • 1
    It seems to me that valgrind is telling you that you are using the malloced contents of buffer as input to strcat without first initializing the contents of buffer. Doing `*buffer=0;` after malloc might be a good way to initialize an empty string. – Henrik Carlqvist Feb 16 '15 at 20:17
  • The last three functional lines of that else-clause in `strcat_ex` are a blatant memory leak as well, and frankly I cannot fathom how `valgrind` did not report said-problem (or you simply didn't include it in your posted findings). The line `*dest = malloc(sizeof(buffer));` allocates memory equal to the size of a `char*`, then prompts leaks it two lines later. Further more, `sizeOfDest >= 1 + sizeOfDest + strlen(src))` cannot possibly **ever** be true, so that code path is dead (thankfully, as it makes no sense regardless). I think "My code works fine" needs to be re-evaluated. – WhozCraig Feb 16 '15 at 20:19
  • Not only is `sizeOfDest = strlen(*dest);` telling you *nothing* about how much string space is available, but `sizeOfDest >= 1 + sizeOfDest + strlen(src)` is nonsense since `sizeOfDest` appears on both sides of the test. **Plus** it's a very dogmatic remark to say "everything else is known to be correct" when you don't know what is causing errors... whether they are direct consequences or indirect. – Weather Vane Feb 16 '15 at 20:24
  • @WhozCraig LOL. I added that line recently to try to correct the error. – napkinsterror Feb 16 '15 at 20:24
  • @napkinsterror you need to work `n` into the logic of whether you need to resize or not. It (`*n`), hold the known size of the destination buffer. Whether there is space available for the concatenation is a function of the current space dictated by `*n` compared to the current length of `*dest`, the length of `src`, and the addition of a terminator. None of that is currently done. – WhozCraig Feb 16 '15 at 20:26
  • 1
    It's still nonsense with `sizeOfDest` both sides As @WhozCraig implied `sizeOfDest >= 1 + sizeOfDest + strlen(src)` is `0 >= 1 + strlen(src)` which is `-1 >= strlen(src)`. And apart from that, it **still** won't tell you how much string space is available. – Weather Vane Feb 16 '15 at 20:30
  • Edit: @WeatherVane I do not understand how this is nonsense? I did previously have `code sizeOfDest = sizeof(*dest)` to determine the space allocated, but decided to change it. Nonetheless, I am seeing if the space allocated is big enough to contain itself, another string, and the null byte.
    Edit: Nevermind. changed it to `code sizeOfDest = sizeof(*dest)` and `code sizeOfDest >= 1 + strlen(*dest) + strlen(src)`. Give me a second to implement your guys extremely useful hints.
    – napkinsterror Feb 16 '15 at 20:33
  • It doesn't determine the space allocated, it determines how much is used. – Weather Vane Feb 16 '15 at 20:33

2 Answers2

3

Your current function is utterly broken. It contains logic that cannot possible see fruition, at least one memory leak, and unchecked concatenation to a target buffer that is uninitialized. Among the things wrong, which are numerous:

  • Assuming sizeofDest dictates not just the storage of the current target string, but also the capacity of any concatenation operation. This is completely wrong, and is why n is provided to this function.
  • Outright memory leak: *dest = malloc(sizeof(buffer)); not only allocates a completely incorrect size of memory (the size of a pointer; not what it points to), it summarily leaks said allocation just two lines later.
  • Dead-code boolean logic: Given any non-negative value N, the expression N >= N + 1 + M, where M is a non-negative value, is impossible to ever be true.
  • You never use the key piece of information provided in tandem with the target pointer-by-address: the current target buffer size provided by n. That value is critical to this algorithm, as it is what dictates the real size of the target buffer, and in conjunction with the current string length in *dest*, will ultimately dictate whether a resize is required.

This is one way to do this function correctly:

char *strcat_ex(char ** dest, int * n, const char * src)
{
    size_t dst_len = 0, src_len = strlen(src);

    // determine current string length held in *dest
    if (*dest && **dest)
        dst_len = strlen(*dest);

    size_t req_len = dst_len + src_len + 1;

    // is space already available for the concatination?
    if (*dest && *n >= req_len)
    {
        // we already know where the target address of the
        // concatination is, and we already know the length of
        // what is being copied. just copy chars.
        if (src_len)
            memcpy(*dest+dst_len, src, src_len+1);
    }
    else
    {
        // resize is required
        void *tmp = realloc(*dest, req_len);
        if (tmp != NULL)
        {
            // resize worked, original content of *dest retained, so
            // we can once again simply copy bytes.
            *dest = tmp;
            memcpy(*dest+dst_len, src, src_len+1);
            *n = (int)req_len;
        }
        else
        {
            perror("Failed to resize target buffer");
            exit(EXIT_FAILURE);
        }
    }

    return *dest;
}

I take great issue with the author of this design in that they chose int for the variable that holds the magnitude of the destination buffer. That magnitude can clearly never be negative, and it makes no sense to use anything other than the same type used by all standard library size-operations, size_t. I'd bring that to the attention of whoever designed this.

Simple Test

int main()
{
    char *dst = NULL;
    int n = 0;

    strcat_ex(&dst, &n, "some string");
    printf("%s : %d\n", dst, n);
    strcat_ex(&dst, &n, " more data");
    printf("%s : %d\n", dst, n);

    *dst = 0; // zero-term the string

    strcat_ex(&dst, &n, "after empty");
    printf("%s : %d\n", dst, n);
    strcat_ex(&dst, &n, " and more");
    printf("%s : %d\n", dst, n);
    strcat_ex(&dst, &n, " and still more");
    printf("%s : %d\n", dst, n);
}

Output

some string : 12
some string more data : 22
after empty : 22
after empty and more : 22
after empty and more and still more : 36

Your Test

Running your test program yields the following:

src="World!";
dest=NULL;
strcat_ex(&dest, &n, src);
 --> gives World! with n=7
Then strcat_ex(&dest, &n, "") yields --> gives World! with n=7
Then strcpy(dest,"abc"); strcat_ex(&dest, &n, "def") yields --> gives abcdef with n=7
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • `if (*dest && **dest)` it was brave of OP to use a double **pointer! – Weather Vane Feb 16 '15 at 20:57
  • @WeatherVane It was by design. I don't like calling `strlen` unless I know there's something there to walk. Cheap check for a zero-length string prior to swimming the hammer =P – WhozCraig Feb 16 '15 at 21:00
  • 1
    @WeatherVane No, no i understand. I wasn't sure if some casual reader understood the purpose of that check prior to firing `strlen`. S'all good, sir. – WhozCraig Feb 16 '15 at 21:13
  • 1
    @WhozCraig I like that strlen hint. Seems very helpful as I have run into a couple problems. Thanks for the answer. I did keep my answer more similiar to my original and it now seems to be working perfectly. You gave me what I asked for a solution and a good understanding of my fault in understanding. My understanding of the size_t vs int is that is does not matter much (bad reason I know), but it was do to integer promotion since mathematical operations are performed on it anyway. It allows for easier use with less understanding of C. (I could be wrong. I've wrong enough today). – napkinsterror Feb 16 '15 at 21:15
  • 1
    @napkinsterrorif If the assignment mandate is to only use `malloc` and `free` it is a wee bit more complicated on the reallocation logic, and frankly a waste, as this is what `realloc` was *made for*. So long as you understand the way it works and how it addresses the *problem* you're trying to solve, so much the better. Glad it helped. Best of luck. – WhozCraig Feb 16 '15 at 21:18
  • @WhozCraig Your spot on with this being about malloc and free so I stayed away from realloc and calloc, but I've used them before. I love solving my problems, but I also love understanding them fully. So if you want to point me to some reference on the reallocation logic feel free. I am rough on my C, but I have been programming in python, java, objective-c, and a lot assembly and c-assembly. I have a good understanding of heaps and stacks and low-level programming. Would love to know more of what I don't know. – napkinsterror Feb 16 '15 at 21:45
1

If *dest is NULL, you will be calling strcat on buffer when buffer has just been malloc'd and it is uninitialized. Either set buffer[0] to 0, or use calloc.

Also, you allocate *dest and then set *dest to buffer two lines later, which leaks some memory. There is no need for the first assignment to *dest.

JS1
  • 4,745
  • 1
  • 14
  • 19
  • Thank you! This alongside the hints pointed to by WeathVane, WhozCraig, andHenrikCarlqvist were all very helpful. It seems to have worked and I will post all my corrections once I do some more thorough testing. – napkinsterror Feb 16 '15 at 21:04