String copy in C
Tags: c, algorithms, beginners
Published: 2023-08-12 09:34:59

*Originally posted at [sjmulder.nl](https://sjmulder.nl/2023/casey-interview-2.html)*.

Casey of *[Molly Rocket](https://www.youtube.com/@MollyRocket)* posted [four interview questions](https://www.youtube.com/watch?v=DS7ygFv84yk) asked for his 1994 Microsoft intership.

This page is about the second: **implementing a string copy function**:

> Implement [strcpy()](http://man.openbsd.org/strcpy), which copies a string into another buffer (we don't care about the return value for now):
>
> ```c
> char *strcpy(char *src, char *dst);
> ```

Let's get this out of the way: using this function is usually a *bad idea* because you need to be absolutely sure that the destination buffer can fit the full source string. Instead, use [strcpy_s()](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strcpy-s-wcscpy-s-mbscpy-s?view=msvc-170) if available, [strclpy()](http://man.openbsd.org/strlcpy), or even [snprintf()](http://man.openbsd.org/snprintf) (`snprintf(dst, sizeof(dst), "%s", src)`).

Let's do a simple for-loop copy first:

```c
void strcpy_1(char *src, char *dst)
{
        size_t len, i;

        len = strlen(src);

        for (i=0; i < len; i++)
                dst[i] = src[i];
            
        dst[len] = '\0';
}
```

We find the length of the string, then copy it over one char at a time. Don't forget the null termination!

But since *strlen* has to walk the string to find the null terminator, we's looping over the string twice! See by expanding strlen:

```c
void strcpy_2(char *src, char *dst)
{
        size_t len=0, i;

        for (i=0; src[i] != '\0'; i++)
                len++;
        for (i=0; i < len; i++)
                dst[i] = src[i];

        dst[len] = '\0';
}
```

Let's put that in one loop:

```c
void strcpy_3(char *src, char *dst)
{
        size_t i;

        for (i=0; src[i] != '\0'; i++)
                dst[i] = src[i];

        dst[i] = '\0';
}
```

This works perfectly fine, but we can avoid having the *i* variable altogether by incrementing *src* and *dst* directly. Walking a pointer like that is a common idiom in C:

```c
void strcpy_4(char *src, char *dst)
{
        while (*src != '\0') {
                *dst = *src;
                src++;
                dst++;
       }
}
```

Our final change is to simplify this version by removing the explicit *\\0* comparison and folding the increment expressions into the assign statement - which may make your hair stand up but this access-and-increment pattern is so common that it's a useful trick to know:

```c
void strcpy_5(char *src, char *dst)
{
        while (*src)
                *dst++ = *src++;

        *dst = '\0';
}
```

Let's dissect that: `*dst++` is `*(dst++)`. The `++` here is *post-increment*, which means that first the old value is returned, and only after the statement the new value is assigned to *dst*. So first `*dst = *src` is performed, and only then are *dst* and *src* incremented - just like in the previous version.

Perhaps a lot to grasp for those unfamiliar with this pattern, but again it's a often-used solution to this common situation. For a different take, here's how OpenBSD [implements](https://cvsweb.openbsd.org/src/lib/libc/string/strcpy.c?rev=1.10&content-type=text/x-cvsweb-markup) the function with a for loop instead (and also returning the copied string, per spec):

```c
char *
strcpy(char *to, const char *from)
{
    char *save = to;

    for (; (*to = *from) != '\0'; ++from, ++to);
    return(save);
}
```

### Appendum

Casey's [video on this question](https://www.youtube.com/watch?v=sabeq-FtfGw) is out now. His solution was:

```c
for (int i=0; (dst[i] = src[i]); i++) ;
```

I hadn't thought to use the result of the assignment as the loop condition. Now you also don't need the extra \\0 assignment because that happens in the last iteration of the loop.

Another version from the video:

```c
while (*dst++ = *src++) ;
```

In a Borland C compiler of the time the first version generates faster code.

Comments welcome on [Mastodon](https://bsd.network/web/@sjmulder/110826417979417010) or below.