C strcpy() - evil?

前端 未结 17 1309
梦毁少年i
梦毁少年i 2020-12-23 10:50

Some people seem to think that C\'s strcpy() function is bad or evil. While I admit that it\'s usually better to use strncpy() in order to avoid bu

相关标签:
17条回答
  • 2020-12-23 10:54
    char *strdup(const char *s1)
    {
      char *s2 = malloc(strlen(s1)+1);
      if(s2 == NULL)
      {
        return NULL;
      }
      strcpy(s2, s1);
      return s2;
    }
    

    Problems:

    1. s1 is unterminated, strlen causes the access of unallocated memory, program crashes.
    2. s1 is unterminated, strlen while not causing the access of unallocated memory access memory from another part of your application. It's returned to the user (security issue) or parsed by another part of your program (heisenbug appears).
    3. s1 is unterminated, strlen results in a malloc which the system can't satisfy, returns NULL. strcpy is passed NULL, program crashes.
    4. s1 is unterminated, strlen results in a malloc which is very large, system allocs far too much memory to perform the task at hand, becomes unstable.
    5. In the best case the code is inefficient, strlen requires access to every element in the string.

    There are probably other problems... Look, null termination isn't always a bad idea. There are situations where, for computational efficiency, or to reduce storage requirements it makes sense.

    For writing general purpose code, e.g. business logic does it make sense? No.

    0 讨论(0)
  • 2020-12-23 10:56

    In the situation you describe, strcpy is a good choice. This strdup will only get into trouble if the s1 was not ended with a '\0'.

    I would add a comment indicating why there are no problems with strcpy, to prevent others (and yourself one year from now) wondering about its correctness for too long.

    strncpy often seems safe, but may get you into trouble. If the source "string" is shorter than count, it pads the target with '\0' until it reaches count. That may be bad for performance. If the source string is longer than count, strncpy does not append a '\0' to the target. That is bound to get you into trouble later on when you expect a '\0' terminated "string". So strncpy should also be used with caution!

    I would only use memcpy if I was not working with '\0' terminated strings, but that seems to be a matter of taste.

    0 讨论(0)
  • 2020-12-23 10:56

    This answer uses size_t and memcpy() for a fast and simple strdup().

    Best to use type size_t as that is the type returned from strlen() and used by malloc() and memcpy(). int is not the proper type for these operations.

    memcpy() is rarely slower than strcpy() or strncpy() and often significantly faster.

    // Assumption: `s1` points to a C string.
    char *strdup(const char *s1) {
      size_t size = strlen(s1) + 1;
      char *s2 = malloc(size);
      if(s2 != NULL) {
        memcpy(s2, s1, size);
      }
      return s2;
    } 
    

    §7.1.1 1 "A string is a contiguous sequence of characters terminated by and including the first null character. ..."

    0 讨论(0)
  • 2020-12-23 10:58

    I agree. I would recommend against strncpy() though, since it will always pad your output to the indicated length. This is some historical decision, which I think was really unfortunate as it seriously worsens the performance.

    Consider code like this:

    char buf[128];
    strncpy(buf, "foo", sizeof buf);
    

    This will not write the expected four characters to buf, but will instead write "foo" followed by 125 zero characters. If you're for instance collecting a lot of short strings, this will mean your actual performance is far worse than expected.

    If available, I prefer to use snprintf(), writing the above like:

    snprintf(buf, sizeof buf, "foo");
    

    If instead copying a non-constant string, it's done like this:

    snprintf(buf, sizeof buf, "%s", input);
    

    This is important, since if input contains % characters snprintf() would interpret them, opening up whole shelvefuls of cans of worms.

    0 讨论(0)
  • 2020-12-23 10:59

    I personally am of the mindset that if the code can be proven to be valid—and done so quickly—it is perfectly acceptable. That is, if the code is simple and thus obviously correct, then it is fine.

    However, your assumption seems to be that while your function is executing, no other thread will modify the string pointed to by s1. What happens if this function is interrupted after successful memory allocation (and thus the call to strlen), the string grows, and bam you have a buffer overflow condition since strcpy copies to the NULL byte.

    The following might be better:

    char *
    strdup(const char *s1) {
      int s1_len = strlen(s1);
      char *s2 = malloc(s1_len+1);
      if(s2 == NULL) {
        return NULL;
      }
    
      strncpy(s2, s1, s1_len);
      return s2;
    }
    

    Now, the string can grow through no fault of your own and you're safe. The result will not be a dup, but it won't be any crazy overflows, either.

    The probability of the code you provided actually being a bug is pretty low (pretty close to non-existent, if not non-existent, if you are working in an environment that has no support for threading whatsoever). It's just something to think about.

    ETA: Here is a slightly better implementation:

    char *
    strdup(const char *s1, int *retnum) {
      int s1_len = strlen(s1);
      char *s2 = malloc(s1_len+1);
      if(s2 == NULL) {
        return NULL;
      }
    
      strncpy(s2, s1, s1_len);
      retnum = s1_len;
      return s2;
    }
    

    There the number of characters is being returned. You can also:

    char *
    strdup(const char *s1) {
      int s1_len = strlen(s1);
      char *s2 = malloc(s1_len+1);
      if(s2 == NULL) {
        return NULL;
      }
    
      strncpy(s2, s1, s1_len);
      s2[s1_len+1] = '\0';
      return s2;
    }
    

    Which will terminate it with a NUL byte. Either way is better than the one that I quickly put together originally.

    0 讨论(0)
  • 2020-12-23 11:00

    memcpy can be faster than strcpy and strncpy because it does not have to compare each copied byte with '\0', and because it already knows the length of the copied object. It can be implemented in a similar way with the Duff's device, or use assembler instructions that copy several bytes at a time, like movsw and movsd

    0 讨论(0)
提交回复
热议问题