4

I'm a beginner in C++ and trying to get my head around every new concept I come across by writting as many various little programs (programmlets) as possible. So I've just concocted the following piece of code:

#include <iostream>
using namespace std;

int main(){
int inumbers[] = {1 ,2 , 3, 4, 5};
int *p;
int i;

p = inumbers;

for(i = 0; p[i]; i++) cout << p[i] << '\n';

return 0;

}

And I fail to understand a seemingly simple thing: how does a compiler "know" when to stop incrementing the loop variable "i"? Surprisingly, the code does work the way it was supposed to.

Albert
  • 2,146
  • 10
  • 32
  • 54
  • 2
    That's a runtime decision, not a compile-time decision. The loop ends when `p[i]` evaluates to false, i.e. when `p[i]` equals zero. Since there's no zero in your array you're reading outside the bounds of your array, which you of course shouldn't do. – Michael Mar 29 '16 at 15:01
  • @Michael Ooh...got it...yeah...that was obviuos! Many thanks! – Albert Mar 29 '16 at 15:05
  • 3
    Before you take on bad habits, please read http://stackoverflow.com/questions/1452721/why-is-using-namespace-std-in-c-considered-bad-practice, and be careful to format/indent your code consistently. – Mat Mar 29 '16 at 15:11

5 Answers5

10

The compiler does not know when to stop.

for(i = 0; p[i]; i++)

will only stop when p[i] is 0. You just got lucky and p[5] happened to be 0. There is nothing saying that it has to be so and it could keep going like this example. As soon as i >= array size you are accessing memory the array does not own and you enter the world of undefined behavior.

Also if you had a 0 in the array like

int inumbers[] = {1 ,2 , 0, 4, 5};

then the program will only print

1
2

And it will not visit the rest of the array.

When dealing with an array or a standard container you can use a ranged based for loop like

for (const auto & e : inumbers)
    std::cout << e << '\n';

To iterate through it's contents. Then const & is not really needed in this case as we are dealing with an int but it is a good habit to get into as passing by reference avoids copying and making it const prevents you from accidentally modifying the element when doing a read only operation.

When dealing with a pointer you need to provide a size like

for (int i = 0; i < size_of_pointed_to_array, i++)
    std::cout << p[i] << '\n';
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Oh I see...yeah that was quite obvious...however, as far as I understand that technic may be successfully applied to strings. – Albert Mar 29 '16 at 15:07
  • 2
    @Albert, that is because `char* s = "Albert";` is equivalent to `char s[] = { 'A', 'l', 'b', 'e', 'r', 't', '\0' };`, _i.e._ the compiler makes sure to allocate an extra element and set it to null (`'\0' == 0`). – CompuChip Mar 29 '16 at 15:09
  • 2
    @Albert It works with null terminated strings. a null terminated string has a `0` at then end so that is why it works. – NathanOliver Mar 29 '16 at 15:09
  • @CompuChip yap:) that's what I meant! – Albert Mar 29 '16 at 15:12
3

You got lucky. By random chance, it appears that there was a zero in memory after the last element in your inumbers array.

Your program is not actually correct. Instead, you might want to limit your loop by counting array members:

for(i = 0; i < sizeof(inumbers)/sizeof(int); i++) cout << p[i] << '\n';
AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
Logicrat
  • 4,438
  • 16
  • 22
  • 1
    Note that the limit here is `sizeof(inumbers)/sizeof(int)` and not `sizeof(p)/sizeof(int)`. The OP assigned `inumbers` to the pointer `p`; if this loop was used outside the scope of the `inumbers` identifier, this technique cannot work, and trying to make it work will just lead to frustration. – AJNeufeld Mar 29 '16 at 15:09
  • @AJNeufeld You are correct. My intent was to introduce a limiting technique within the context of OP's question as it was asked. – Logicrat Mar 29 '16 at 15:34
  • Note that if you use `sizeof(inumbers)/sizeof(inumbers[0])` It is one less thing that needs to be changed if you change the data type of the array. – NathanOliver Mar 30 '16 at 11:51
  • @NathanOliver Yes, that's even better. – Logicrat Mar 30 '16 at 12:02
  • @NathanOliver well it's true that using `sizeof(inumbers)/sizeof(inumbers[0])` is somewhat better in that sense but it looks somewhat ugly...I mean why is it `inumbers[0]` and not, say, `inumbers[1]` (considering that your array consists of more than one element)...I perfectly understand why...but it just looks uglier, in my opinion. Do you agree? – Albert Mar 31 '16 at 07:52
  • @Albert I think it looks fine. It expresses exactly what type we want to divide the size of the array by where just having `int` makes you double check if the array is really an `int` array and not something else. The reason you use `inumbers[0]` is zero sized arrays are not allowed. So if the array has to have at least one element then `inumbers[0]` is guaranteed to exist. The same can not be said for any other index – NathanOliver Mar 31 '16 at 11:42
  • I have a macro I use a lot: `#define countof(x) sizeof(x)/sizeof(x[0])`. – Logicrat Mar 31 '16 at 12:37
2

The Issue

The behaviour of your program is undefined. According to the way you have written the program, the loop should terminate when p[i] evaluates to false, which in this case should mean that p[i] == 0.

What C++ is actually doing

Clearly there is no 0 in your array hence the program will take the pointer out of bounds of the array and will dereference 4 consecutive bytes of memory and interpret those 4 bytes as an integer until it finds a 0. In your case you got lucky that there was such a 0 present in memory.

In case there was no 0, the program will eventually encounter a segmentation fault.

An interesting experiment would be printing the value of i after it exits the loop.

Banach Tarski
  • 1,821
  • 17
  • 36
  • actually using the same sort of logic I tried doing that kind of an experiment before I asked that question and it happened to print all the numbers from zero to 4...I repeated it a few times with the same outcome haha well now I realise how dumb that was...don't know what made me do that...I'm well aware that things like that can be dengerous. – Albert Mar 30 '16 at 21:19
2

The above code relies upon undefined behavior.

In particular, it reads past the end of the array. As it happens that int sized quad byte is zero (in your particular build and hardware). This causes the loop to end (the middle clause of the for loop reads while p[i] is non-zero).

When there is undefined behavior, the C++ language does not constrain the behavior of the runtime. "Working" is a valid behavior, as is formatting your hard drive.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

You are iterating past the last member of your array and are out of bounds. You are getting lucky because p[i] where i is 5 is evaluating to false or 0. For example, change the second value in the array to 0 and check out when the loop stops.