1

I'm trying to implement the strcpy function by myself. The original strcpy is part of the the string.h library.

char *strcpy(char *dest, const char *src)
{
    assert(dest != NULL && src != NULL);
    char *temp = dest;
    while (*src)    
    {
        *dest = *src;
        src++;
        dest++;
    }
    return temp;
}
            
void strcpyTest()
{
   char source[20] = "aaaaaa";
   char dest1[20] = "bbbbbbbbb";
   char desta[10]="abcd";
   puts(dest1); // bbbbbbbbb
   strcpy(dest1, source);
   puts(dest1); // aaaaaa
   strcpy(desta, source);
   puts(desta); // aaaaaa
   strcpy(desta, dest1);
   puts(desta); // aaaaaa
   strcpy(dest1, desta);
   puts(dest1); // aaaaaa
   strcpy(source, desta); 
   puts(source); // aaaaaa
}

As you can see, even the first call for the function with a longer dest than the src gives the right result although, by logic, it should give aaaaaabb and not aaaaaa:

   char source[20] = "aaaaaa";
   char dest1[20] = "bbbbbbbbb";
   strcpy(dest1, source);
   puts(dest1);
   /** aaaaaa **/

Why does my function work? I would guess that i'll have to manually add the /0 char in the end of *dest* after the while (*src)` will exit.

I mean the whole point of this while (*src) is to exit when it reaches the end of *src* which is the last char in the string which is /0. Therefore, I would guess i'll have to add this character to *dest* by myself but the code somehow works and copies the string without the manual addition of /0.

  1. So my question is why and how it still works?

  2. When I create a new array, lets say int *arr or char *arr, of 10, i.e char arr[10] or int arr[10] and I initialize only the 2 first indexes, what happens to the values that inside the rest of the indexes? Does they will be filled with zeros or garbage value or what?

Maybe my code works because it filled with zeros and that's why the while loop stops?

NoobCoder
  • 513
  • 3
  • 18
  • 1
    When you initialize an array, any elements not explicitly set will be set the same way as if it were a static variable, which is to say the elements not explicitly initialized will be implicitly initialized to 0. – Christian Gibbons Feb 17 '21 at 22:43
  • It works by pure accident. Your destination arrays happen to have `\0` at the places that happen to give you the "right" results. – Eugene Sh. Feb 17 '21 at 22:44
  • @EugeneSh. I also thought about that, that's why I tried with 3 different strings and different ways to use this function, and in all the calls it worked. So it worked for all function calls by pure accident? – NoobCoder Feb 17 '21 at 22:45
  • yes. Try not initializing the destination arrays for example. Or initialize destination to a longer string than the source. – Eugene Sh. Feb 17 '21 at 22:46
  • @ChristianGibbons So any elements that won't be get a value from me, will be initialized to `0`? Is it right also for `int` arrays or only for `char` arrays? `P.S` as you can see, some people here in the comments say that `0` isn't guaranteed. – NoobCoder Feb 17 '21 at 22:48
  • Give me a minute, I'm getting relevant passages from the C standard. I've got the section on initialized arrays, but it refers back to the rules for static storage duration objects, which I am now looking up. – Christian Gibbons Feb 17 '21 at 22:56
  • @EugeneSh. Hey, please check the new addition that I added to the code. The first call for the function. I tried it with a longer `dest` than the `source` and it still works. – NoobCoder Feb 17 '21 at 22:58
  • 1
    @NoobCoder I would expect it to print `aaaaaabb`. Am I right? – Eugene Sh. Feb 17 '21 at 23:00
  • @EugeneSh. As I added as a comment in the end of the line, it prints `aaaaaa`. That's why I opened this question, because everything seems to work perfectly, but it shouldn't because I did not add the `/0` char. – NoobCoder Feb 17 '21 at 23:01
  • 2
    Sorry, can't reproduce. Doublecheck it. https://ideone.com/xHqWWz - this is printing as per my expectation (miscounted `b`s though) – Eugene Sh. Feb 17 '21 at 23:03
  • @ElliottFrisch See my answer on why it is well-defined behavior and not unspecified. – Christian Gibbons Feb 17 '21 at 23:28
  • 1
    Replacing a library function with your own function of the same name is expressly forbidden by the C standard. So the compiler is allowed to use its own `strcpy` even if you supply a different one. Try changing the name of your function, or at least put a `printf` at the start of your function to make sure that's it's actually being called. – user3386109 Feb 17 '21 at 23:37

3 Answers3

4

For starters you should select another name instead of strcpy.

Let's consider all the calls of your function step by step.

The variable source is declared like

char source[20] = "aaaa";

This declaration is equivalent to the following declaration

char source[20] = 
{ 
    'a', 'a', 'a', 'a', '\0', '\0', '\0', '\0', '\0', '\0', 
    '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'
};

because according to the C Standard elements of the array that were not explicitly initialized are implicitly initialized by zeroes.

The variable desta is declared like

char desta[10]="abcd";

This declaration is equivalent to the following declaration

char desta[10]= { 'a', 'b', 'c', 'd', '\0', '\0', '\0', '\0', '\0', '\0' };

So the first call

strcpy(desta, source);

just substitute four characters "abcd" for four characters "aaaa". The result array desta will contain a string because nether terminating zero is overwritten.

After this call

strcpy(desta, dest1);

the array desta will contain the string "bbbbbbbbb" because the last zero character of the array desta is not overwritten by this call.

This call

strcpy(dest1, desta);

in fact is not being changed the array dest1.

In this call

strcpy(source, desta);

as all the zero characters of the array source were not overwritten the array will contain a string.

You could get an unpredictable result if you called at first

strcpy(desta, dest1);

and then

strcpy(desta, source);

because your function does not append a terminating zero to the destination array.

Here is a demonstrative program.

#include <stdio.h>
#include <assert.h>

char * my_strcpy(char *dest, const char *src)
{
    assert(dest != NULL && src != NULL);
    char *temp = dest;
    while (*src)    
    {
        *dest = *src;
        src++;
        dest++;
    }
    return temp;
}

int main(void) 
{
    char source[20] = "aaaaaa";
    char dest1[20] = "bbbbbbbbb";
    char desta[10]="abcd";
    
    my_strcpy(desta, dest1);
    my_strcpy(desta, source);
    
    puts( desta );

    return 0;
}

The program output is

aaaaaabbb

That is the desta contains the string "aaaaaabbb" instead of the string aaaaaa.

The updated function could look the following way

char * strcpy(char *dest, const char *src)
{
    assert(dest != NULL && src != NULL);
    char *temp = dest;

    while ( ( *dest++ = *src++ ) );    

    return temp;
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Thanks Vlad. Other people here wrote that the initialization of '\0' as the value of the indexes I didn't not initialize is not guaranteed. What do you think about it? Is it true? What about `int arrays` or other type than `char` arrays? – NoobCoder Feb 17 '21 at 23:12
  • @NoobCoder This is guaranteed by the C Standard. Elements of an array that do not have explicit initializers (provided that array is initialized) are implicitly initialized by zeroes. – Vlad from Moscow Feb 17 '21 at 23:15
  • I just compiled the suggestion that you have added in the end of your answer. First `strcpy(desta, desti);` and then `strcpy(desta, source);` and everything works fine. Output after first `strcpy(desta, desti);` is : `desti:bbbbbbbbb , desta:bbbbbbbbb` and Output after `strcpy(desta, source);` is: `source:aaaaaa , desta:aaaaaa`. So everything got copied fine. – NoobCoder Feb 17 '21 at 23:30
  • @NoobCoder See my updated answer. It seems you are calling the standard string function strcpy instead of your function strcpy. – Vlad from Moscow Feb 17 '21 at 23:43
  • You are right lol, I just fixed it and as you told me, it does not copy as it should as I had to add the `\0` manually in the end of the copy. One last question - why does `while ( ( *dest++ = *src++ ) ); ` work? How does it work without the need to add the `\0` in the end? I mean, it puts the value of *src in *dest, and then promotes both of them. Where is the `\0` here? – NoobCoder Feb 18 '21 at 09:33
  • @NoobCoder It adds the terminating zero as a result the condition will be equal to 0 after the assignment because it is the value of the assignment expression. – Vlad from Moscow Feb 18 '21 at 09:44
  • but why will it first make the assignment of zero and only then it will exit the `while `loop? the `while` should exit when it reaches zero, and here somehow it first makes the assignment of zero and only then it reaches the `while` which exits – NoobCoder Feb 18 '21 at 09:47
  • @NoobCoder To make it more clear you can rewrite the condition the following way while ( ( *dest++ = *src++ ) != 0 ); SO as soon as you assigned the terminating zero the condition evaluates to false because the value of the condition is the value of the assignment. – Vlad from Moscow Feb 18 '21 at 09:48
0

Correct that this function will not add a \0 to the end of the dest string. You will need to add a final \0 assignment to dest.

Why does it seem to work as-is? It "works" because your initialization of dest just happens to place a \0 character at the right point in the string. These cases are honestly "unlucky" as they hide all sorts of problems. Another case where this can happen is if you are running a debug build where memory is automatically set to 0 and therefore the final set bug is hidden.

So my question is why and how it still works?

If you initialize the first 2 values on an array, the rest are considered to be garbage, again exactly as you state. The exception to this would be if the array were "global" or "static". In these cases, the compiler will set them to 0 for you.

Michael Dorgan
  • 12,453
  • 3
  • 31
  • 61
  • It doesn't have to do with debug, it has to do with the rules of array initialization. – Christian Gibbons Feb 17 '21 at 22:44
  • Ahh - missed that – Michael Dorgan Feb 17 '21 at 22:45
  • Just to be clear, if you initialize *any* element of an auto storage lifetime array, then all of the elements not explicitly initialized will be 0, not garbage. It's only garbage if you don't initialize the array at all. – Christian Gibbons Feb 17 '21 at 23:21
  • @ChristianGibbons When you say "if you don't initialize the array at all" what do you exactly mean? Like if I don't initialize even one index inside of it? – NoobCoder Feb 17 '21 at 23:34
  • @NoobCoder Yes. If you declare an array without an initializer, such as with `int x[20];` and it has automatic storage duration, then the values of the elements would be garbage. – Christian Gibbons Feb 17 '21 at 23:51
0

Your arrays are padded with zeros, which are equivalent to the null-terminator '\0'. When you initialize an array, any elements not explicitly set will be set the same way as if it were a static variable, which is to say the elements not explicitly initialized will be implicitly initialized to 0. So in this case, your strings just happen to have a null-terminator after you finished your copy because when you initialized the array, all of the values not explicitly set by your initializer were set to 0.

If you copy one a 4-character string into a buffer holding an 8-character string, you'll only see the first 4 characters changed in your destination string while leaving another 4 characters still there before you hit a null-terminator.

From the C11 Standard Working Draft 6.7.9 p21

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

So according to the above passage, because you initialized some elements of the array, the elements that you did not explicitly initialize will be treated the same as if it were a static-storage duration object. So for the rules for statuc storage duration, we have 6.7.9 p10:

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, then:

— if it has pointer type, it is initialized to a null pointer;

— if it has arithmetic type, it is initialized to (positive or unsigned) zero;

— if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;

— if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;

The above passage tells us that every member of the aggregate (an array in this case) will be initialized as per the element's rules, and in this case, they are of type char, which is considered an arithmetic type, which the rules states will be initialized to 0.

Christian Gibbons
  • 4,272
  • 1
  • 16
  • 29