3

My college professor recently gave us a task of implementing our own smart pointer classes. In his boilerplate code for copying strings I found this piece beautiful syntactic sugar:

while (*sea++ = *river++);// C Sting copy 

I looked further into this code and found that it is the exact code as found in strcpy.c and further explanation of how it works, in the following stackoverflow question: How does “while(*s++ = *t++)” copy a string?

When I tried to use this syntactic sugar in my following code it gave garbage as a result and deleted the string stored in "river":

    #include<iostream>
    #include<cstring>

    using namespace std;
        void main()
        {
            const char *river = "water";// a 5 character string +  NULL terminator

            char *sea = new char[6];

            while (*sea++ = *river++);

            cout << "Sea contains: " << sea << endl;
            cout << "River contains: " << river << endl;
        }

RESULT:
Result of the above code

I know that I can simply achieve the desired result with the following code instead:

    int i = 0;
    while (i<6)
    {
        sea[i] = river[i];
        i++;
    }

But this is not the answer I want. I would like to know is there something wrong with the implementation of my while loop or the instantiation of my char pointers?

Community
  • 1
  • 1
Rafay Khan
  • 1,070
  • 13
  • 25
  • 4
    Because both pointers are moving beyond their boundaries, resulting in undefined behavior. You should make pointer copies of their initial addresses, in case if you want to do this way. (e.g. `const char* riverIterate = river` & `char* seaIterate = sea`. BTW, `main()` must return `int` and in C++, it's recommended to use `std::string`. – iammilind Aug 04 '16 at 06:30
  • Thanks! Understood. Can't use std::string as I have to make my own implementation of it for my assignment. – Rafay Khan Aug 04 '16 at 06:40
  • @TimCastelijns: already done. But I wold like people to know, hence the comment. Some "mods" have to my guess a too fast trigger! – Emilio Garavaglia Aug 04 '16 at 07:05
  • Related: A debugger and single-stepping this would have made *very* short work of finding the problem. Seriously. The sooner you become well-versed with one, the better off you'll be. A hint as to the problem sans-debugger would have been consideration of what `strcpy` does with the provided pointers after copying (nothing) vs. what *you* are doing. – WhozCraig Aug 04 '16 at 07:17
  • @EmilioGaravaglia, Out of bound pointer arithmetic itself is **undefined**, let alone the de-referencing after it. In this Q, both are happening. Why did you reopen? This was the reason, instead of answering, I commented the possible answer and closed it. By linking duplicates, we are helping the future visitors to look at the better question. Can someone please close this with either [Why is out-of-bounds pointer arithmetic undefined behaviour?](http://stackoverflow.com/q/10473573/514235) or [Pointer arithmetic: out of bound without dereferencing](http://stackoverflow.com/q/26626663/514235). – iammilind Aug 04 '16 at 07:26
  • 1
    @iammilind: Yes, "Out of bound pointer arithmetic itself is undefined". The problem is WHY did they go out of bound. And it is for reasons OTHER then the one indicated in the referred question. The fact they lead to the same error, does not means it is because a same reason. We cannot help people learning if we slam the door any time someone walk into an UB. – Emilio Garavaglia Aug 04 '16 at 07:30
  • @WhozCraig Yeah I did see that something weird was going on while using the debugger, but just couldn't make the connection. I my my head kept thinking of the char pointers as arrays, when they are obviously not. Guess this sort of stuff happens when you come back to c++, after strictly doing most of your work in Python. – Rafay Khan Aug 04 '16 at 07:53

3 Answers3

7

You are getting garbage displayed because your pointers are pointing at garbage when you go to display them. You are advancing the pointers while looping, but you need to use the original pointers when displaying the data.

Also, you have a memory leak, as you are not freeing the char[] buffer.

Try this instead:

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    const char *river = "water";// a 5 character string +  NULL terminator

    char *sea = new char[6];

    const char *p_river = river;
    char *p_sea = sea;
    while (*p_sea++ = *p_river++);

    cout << "Sea contains: " << sea << endl;
    cout << "River contains: " << river << endl;

    delete [] sea;
    return 0;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
5

The "error" is technically an undefined behavior due to pointer gone out of bound respect to to the memory they refer.

The interesting part is WHY that happens.

And it is due by the confusion between the pointers and what they point to.

sea and river are not strings. The strings are anonymous variable residing somewhere in memory, with the two pointer indicating their start.

You should never touch them, otherwise you will be no more able to access those strings further.

If you need to move pointers, use other ones.

A more proper example should be this one

 using namespace std;
 int main() //< note `void main` is a C++ dialcet
 {
     // note the use of `const` after the `*`: 
     // you cannot modify these pointers.
     const char * const river = "water";  // a 5 character string +  NULL terminator
     char * const sea = new char[6];

     {
          // in this inner scope, other non-const pointers are
          // initialized to point to the same memory
          const char* r = river;
          char* s = sea;
          while (*s++ = *r++); // the loop moves the mutable pointers
          // note how `river++` or `sea++` is an error, being them `*const`
     }

     // there are no more `r` and `s` here, but `sea` and `river` are still the same.
     cout << "Sea contains: " << sea << endl;
     cout << "River contains: " << river << endl;

     //now we have an array allocated with new to return to the system
     delete[] sea; //< the importance to not move the `sea` pointer
}

Note how delete deletes the array, not the pointer.

To go more in advance, two things can be done.

The first is make the inner scope a proper function:

 using namespace std;

 void copy(const char* r, char* s)
 {
     // in this function, other non-const pointer (parameters) are
     // initialized to point to the same memory upon call
     while (*s++ = *r++); // the loops moves the mutable pointers
     // note how `river++` or `sea++` is an error, being them not visible.
 }

 int main() //< note `void main` is a C++ dialect
 {
     const char * const river = "water";  // a 5 character string +  NULL terminator
     char * const sea = new char[6];

     copy(river, sea);         

     cout << "Sea contains: " << sea << endl;
     cout << "River contains: " << river << endl;

     //now we have an array allocated with new to return to the system
     delete[] sea; //< the importance to not move the `sea` pointer
}

and the second is get rid of the new/delete pair in a same context, using -for example- an std::unique_ptr<char[]>

But this is taking too far!

Emilio Garavaglia
  • 20,229
  • 2
  • 46
  • 63
2

Because ASCII-art is always useful for figuring out pointer problems...

This is before your while loop:

 river points to
 |
 V
+-+-+-+-+-+--+
|w|a|t|e|r|\0|
+-+-+-+-+-+--+

+-+-+-+-+-+-+
|?|?|?|?|?|?|
+-+-+-+-+-+-+
 ^
 |
 sea points to

But after your loop, you have this:

              river points to
              |
              V
+-+-+-+-+-+--+
|w|a|t|e|r|\0|
+-+-+-+-+-+--+

+-+-+-+-+-+--+
|w|a|t|e|r|\0|
+-+-+-+-+-+--+
              ^
              |
              sea points to

So you see, the copy has indeed been performed, but you've also moved the pointers. That means if you try and print them out, you'll get whatever garbage lies after those memory blocks.

Were you to save copies of the pointers from before the while loop, and print those out instead, you'd get the output you're looking for.

Kaz Dragon
  • 6,681
  • 2
  • 36
  • 45