tags:

views:

223

answers:

10

Hello,

gcc 4.4.4 c89

My program does a lot of string coping. I don't want to use the strncpy as it doesn't nul terminate. And I can't use strlcpy as its not portable.

Just a few questions. How can I put my function through its paces to ensure that it is completely safe and stable. Unit testing?

Is this good enough for production?

size_t s_strlcpy(char *dest, const char *src, const size_t len)
{
    size_t i = 0;

    /* Always copy 1 less then the destination to make room for the nul */
    for(i = 0; i < len - 1; i++)
    {
        /* only copy up to the first nul is reached */
        if(*src != '\0') {
            *dest++ = *src++;
        }
        else {
            break;
        }
    }

    /* nul terminate the string */
    *dest = '\0';

    /* Return the number of bytes copied */
    return i;
}

Many thanks for any suggestions,

+1  A: 

Yes, unit testing. Check with lots of randomly generated strings.

Looks fine to me, though.

Tyler Menezes
random test input doesn't make much sense -- if the test finds a failure, how will you duplicate it? Each test run would potentially test different parts of the algorithm and could miss some edge cases altogether.
Bryan Oakley
@Bryan Oakley "if the test finds a failure, how will you duplicate it?" Use pseudo-random inputs, so you can always duplicate it.
Joey Adams
+2  A: 

Why don't you use something like memccpy() instead of rolling your own? You just have to terminate with a null byte, but it's easier and generally faster to augment a standard function than to start from scratch.

Some architectures will heavily optimize or even use assembly for string functions to squeeze good performance out of them.

Without building or debugging:

str = memccpy (dest, src, '\0', len);
if(str)
    *str = '\0';
WhirlWind
I am a junior programmer, so just looking to see what mistakes I make. Well, not to re-invent the wheel. But it more fun to develop your own.
robUK
@robUK there's nothing wrong with reinventing the wheel as a learning exercise, but I'd never do that in production code, as you asked. Also, you disparaged the standard library functions, but you can basically make them work if you try, and it's a much better way to go, again, for production code.
WhirlWind
@robUK One basic reason I would prefer to augment the standard library is if I run into a bug. If I see a standard library call, I probably won't look into it as a bug, but if it's a home grown function, the probability of bugs is higher, particularly if it's not called a lot, so I will spend more debugging time looking into it.
WhirlWind
The OP cannot use `memccpy` for the very same reason they can't use `strlcpy`. It is not portable. There's no such function is C standard library as `memccpy`.
AndreyT
+7  A: 

Never write your own if you can help it.

Ignacio Vazquez-Abrams
I agree on this, just include that in your code base, it's BSD licensed and all.
cjg
+1  A: 

There's always static code analysis. Edit: List of tools for static code analysis

Rooke
In my opinion, that link should point to a static code analyzer for C that you've found useful, not to a page defining it in abstract.
Joey Adams
+9  A: 

Although you could simply use another strlcpy function as another post recommends, or use snprintf(dest, len, "%s", src) (which always terminates the buffer), here are the things I noticed looking at your code:

size_t s_strlcpy(char *dest, const char *src, const size_t len)
{
    size_t i = 0;

No need to make len const here, but it can be helpful since it checks to make sure you didn't modify it.

    /* Always copy 1 less then the destination to make room for the nul */
    for(i = 0; i < len - 1; i++)
    {

Oops. What if len is 0? size_t is usually unsigned, so (size_t)0 - 1 will end up becoming something like 4294967295, causing your routine to careen through your program's memory and crash into an unmapped page.

        /* only copy up to the first nul is reached */
        if(*src != '\0') {
            *dest++ = *src++;
        }
        else {
            break;
        }
    }

    /* nul terminate the string */
    *dest = '\0';

The above code looks fine to me.

    /* Return the number of bytes copied */
    return i;
}

According to Wikipedia, strlcpy returns strlen(src) (the actual length of the string), not the number of bytes copied. Hence, you need to keep counting the characters in src until you hit '\0', even if it exceeds len.

Also, if your for loop terminates on the len - 1 condition, your function will return len-1, not len like you'd expect it to.


When I write functions like this, I usually prefer to use a start pointer (call it S) and end pointer (call it E). S points to the first character, while E points to one character after the last character (which makes it so E - S is the length of the string). Although other people on this site may feel that my technique is ugly and obscure, I've found it to be fairly robust.

Here's an over-commented version of how I would write strlcpy:

size_t s_strlcpy(char *dest, const char *src, size_t len)
{
    char *d = dest;
    char *e = dest + len; /* end of destination buffer */
    const char *s = src;

    /* Insert characters into the destination buffer
       until we reach the end of the source string
       or the end of the destination buffer, whichever
       comes first. */
    while (*s != '\0' && d < e)
        *d++ = *s++;

    /* Terminate the destination buffer, being wary of the fact
       that len might be zero. */
    if (d < e)        // If the destination buffer still has room.
        *d = 0;
    else if (len > 0) // We ran out of room, so zero out the last char
                      // (if the destination buffer has any items at all).
        d[-1] = 0;

    /* Advance to the end of the source string. */
    while (*s != '\0')
        s++;

    /* Return the number of characters
       between *src and *s,
       including *src but not including *s . 
       This is the length of the source string. */
    return s - src;
}
Joey Adams
Note that the begin, and one-past-the-end, is the same technique used in the C++ STL to great effect.
DeadMG
+2  A: 

I would suggest that White-box testing is useful for a function like this (a form of unit testing).

caf
+2  A: 

There is the DRY principle "don't repeat yourself". In other words do not create new code to do something that is already a done deal - check in the standard C library as the example above (WhilrWind) shows.

One reason is the testing mentioned. The standard C library has been tested for years, so it is a safe bet it works as advertised.

Learning by playing around with code is a great idea, keep on trying.

jim mcnamara
The whole point of the question is that the standard library does not provide immediate means to do what the OP wants to do. The answer you are referring to (WhirlWind) is based on non-standard means.
AndreyT
+1  A: 

I think it's a mistake to rely so much on the length, and doing arithmetic on it.

The size_t type is unsigned. Consider how your function will behave if called with a 0-sized destination.

unwind
+2  A: 

IMHO, just barrow the original strlcpy, which Ignacio Vazquez-Abram tersely stated. OpenBSDs code is battletested and the license terms rock ;).

As to your code, something I would add to what has already been said by others, is just a matter of personal taste:

/* only copy up to the first nul is reached */
if(*src != '\0') {
    *dest++ = *src++;
}
else {
    break;
}

I would have written this like this:

if(*src == '\0') {
    break;
}
*dest++ = *src++;

Both because it reduces on the amount of unnecessary code people need to read, and because it is my 'style' to consistently write that way, instead of if (ok) { do } else { handle error }. The comment above the if is also redundant (see comment above the for loop).

TerryP
I totally agree with regards to style (and many others do as well, http://stackoverflow.com/questions/114342/what-are-code-smells-what-is-the-best-way-to-correct-them/223881#223881) ; "normal" code should (when possible) be written without extra indentation, it should be exceptions that are tested for.
hlovdal
+1  A: 

Unit testing? Is this good enough for production?

Probably for a "simple" function like this it may be sufficient, although the only real way to test a function is to try and break it.

Pass to it NULL pointers, 10k character long strings, negative values of len, somehow corrupted data, and so on. In general think: if you were a malicious user trying to break it, how would you do that?

See the link in my response here

nico