2


I was asked to make 2 functions copyString and concatString i did and Implemented them both but at the output that i got תi have been told that it can be done better but never got explained how.
now it is killing me what could i do better so here is the code and i will be happy to hear any suggestions.

void copyString (char **strDst, const char *strSrc)
{
     char *strTmp = NULL;
     int length = strlen (src);
     if (*strDst== NULL) 
     {
        *strDst= malloc (length);   
     }
     else 
     {  
         if (strlen(*strDst) != length)
         {
             strTmp = *strDst;
         }
         *strDst= malloc (length);  
     }
     strcpy (*strDst, strSrc);  
     if (strTmp != NULL)    
         free (strTmp );    
 }

void concatString (char **strDst, const char *cat)
{
     int cat_length = strlen (cat);
     if (cat_length > 0) 
     {  
         *strDst= realloc (*strDst, strlen (*strDst) + cat_length); 
          strcat (*strDst, cat);
     }
}




void main(int argc, char *argv[])
{
    char *str = NULL;
    copyString(&str, "Hello World");
    puts(str);
    copyString(&str,str+6);
    puts(str);
    concatString(&str, " Pesron");
}

The ouput should be as following:
1.Hello World
2. World
3. World Person

Thanks.

roy barak
  • 53
  • 5
  • You should have asked "Why am being asked to re-invent the wheel?" – Alok Save Nov 17 '12 at 14:22
  • @Als: if you can do it correctly, it's a pointless task. Since the questioner *can't* do it correctly it's a worthwhile exercise. Or anyway it will be once someone says what's wrong with this code. – Steve Jessop Nov 17 '12 at 14:24

2 Answers2

4

Errors:

strlen returns the length excluding the nul terminator, so all of your sizes that you allocate are too small.

In the case where if (strlen(*strDst) != length) is false, (that is, the lengths are equal) you leak the old buffer.

realloc and malloc can both fail, you should be able to write code to cope with that.

The correct way to use realloc is:

char *newbuf = realloc(oldbuf, newsize);
if (newbuf == NULL) {
    // handle the error somehow, and note that oldbuf is still allocated
} else {
    oldbuf = newbuf;
}

"Handle the error somehow" might require deciding what to do, depending on what the documentation of your two functions says they do on failure. If it doesn't say then it should.

(Picky) int is not guaranteed to be a large enough type to hold the length of a string. Use size_t (unless maybe you've been strictly forbidden from using unsigned types, in which case there's ssize_t).

Things you can improve:

There's no need to use strTmp the way you do, you could free the string immediately instead of at the end of the function. [Edit: yes there is a need, there seems to be a requirement that copyString but not concatString should permit overlap of source and destination. Personally I'd still write it slightly differently.]

In if (strTmp != NULL) free (strTmp ); the test is redundant since it is valid to call free with a null pointer, and doing so has no effect.

You do *strDst= malloc (length); in both cases in copyString.

main leaks memory since it never frees str.

main should return int, not void.

Here's how I might write them:

Since you can't change the calling code to make it check for error, you have to either abort() or else write something there that it can call puts on. Since the main function was written on the assumption that the calls cannot fail, abort() is probably the least bad solution.

It would probably be better for the caller if the functions return a value indicating success or failure, but we're constrained by the existing calling code. To be honest that's not a totally unrealistic situation to be programming for...

void concatString (char **strDst, const char *cat) {
    size_t dstlen = *strDst ? strlen(*strDst) : 0;
    char *buf = realloc(*strDst, dstlen + strlen(cat) + 1);
    if (!buf) {
        abort();
    }
    strcpy(buf + dstlen, cat);
    *strDst = buf;
}

void copyString (char **strDst, const char *strSrc) {
    char *buf = malloc(strlen(strSrc) + 1);
    if (!buf) {
        abort();
    }
    strcpy(buf, strSrc);
    free(*strDst);
    *strDst = buf;
}
Community
  • 1
  • 1
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • about the main, yes that true about the in and the return at the and i forget to take it off when i posted the question. – roy barak Nov 17 '12 at 14:48
  • with the frist copyString there is a problem when i call the function at the second time. the memory allaction to *tmp not working well (this is the call copyString(&s,s+6);). i will try te second option now – roy barak Nov 17 '12 at 15:20
  • 1
    You can't free, or even modify, strTmp straight away because it looks like it could also be the src. – antak Nov 17 '12 at 15:26
  • @roybarak Calling `copyString` with the source and destination overlapping isn't supported (just like `strcpy`). You'd need another function for that. – Daniel Fischer Nov 17 '12 at 15:27
  • Just to clear something here the main function was giveing to me i had to create the copyString and concatString – roy barak Nov 17 '12 at 15:31
  • @roybarak So it's part of the spec that it should be able to deal with overlapping source and destination. Can it be assumed that `*strDst` is either `NULL` or points to a `malloc`ed block of memory? – Daniel Fischer Nov 17 '12 at 15:38
  • I also thought that i can assumed it but after the feedback i got i am not sure about it. – roy barak Nov 17 '12 at 15:49
  • Oops. I didn't realise that they're allowed to overlap, I didn't read the calling code properly. Allowing `concatString` to overlap too would be a challenge, at least if it's supposed to be efficient in the case where it doesn't. – Steve Jessop Nov 17 '12 at 17:27
0

Besides of what Steve Jessop mentions in his answer, no errors in your sources but missing:

  • validation of input parameters
  • return errors through error value (for example as integer return code of the function, instead of void
alk
  • 69,737
  • 10
  • 105
  • 255
  • Some of the feedback that i got about it is:
    1- Your code may be simplified by using better suited functions.
    2- crashes in some reasonable use cases
    – roy barak Nov 17 '12 at 14:55
  • Some of the feedback that i got about it is: 1- Your code may be simplified by using better suited functions. 2- crashes in some reasonable use cases. Any idea how to prevebt the option of using pointer that was initialized for example copyString(&str,str+6); while *str = NULL – roy barak Nov 17 '12 at 15:01
  • What do you mean by “validation of input parameters”? – Pascal Cuoq Nov 17 '12 at 15:11
  • Doing sanity check on the parameters passed in. In the cases of the OP's functions one shall check the pointers passed in to not equal `NULL` and return appropriate error codes if. `strlen(NULL)` would cause undefined behaviour for example, as well as `*NULL` would. @PascalCuoq – alk Nov 18 '12 at 13:50
  • I see. I thought you had a formula to check that a pointer points to a well-formed string in C (what the function expects, and what it should validate if it was to validate its arguments). You meant that of all pointers that do not point to well-formed strings, there is one that can be checked against, so the function should check for that one. – Pascal Cuoq Nov 18 '12 at 13:59
  • Exactly. As it's C there nothing more one can do, but this little possible one **shall** do. @PascalCuoq – alk Nov 18 '12 at 14:09
  • Thinking more about it, I am still confused. You are right that `strlen(NULL)` or `*NULL` are both undefined behavior, but in practice, on the very largest majority of C implementation, they result in a clean failure. And on the kind of embedded constrained platform where *NULL might not result in clean failure, one does not have the leisure to validate function arguments either. Now, if you do implement argument validation, what can the function do if passed NULL? Fail. The prototype does not allow anything better. So how is calling `strlen(cat)` not **validating** `cat`? – Pascal Cuoq Nov 18 '12 at 14:20
  • You always have the choices when defining an API: 1 add the set of valid values to be passed to the documentation or 2 make the function validate the input and return qualified error codes. I'd prefer 1&2, which obviously would introduce the need to change the API from returning `void` to return `int` to be able to return a error code/status. @PascalCuoq – alk Nov 18 '12 at 14:45