39

I have been asked to describe what these lines of code are doing for a college assignment

int main() { int t1[] = {0,0,1,1,1}, t2[] = {0,0,1,1,1}; int *p1 = t1, *p2 = t2; while (!*p1++ || !*p2++); cout << (p1-t1) << endl; cout << (p2-t2) << endl; } 

My take on it is, 2 arrays of type int are created and filled with values, 2 pointers are created and pointed at each array, then I start to have trouble.

while (!*p1++ || !*p2++); 

To me this is saying while 0 move the position of *p1one place or while 0 move the position of *p2 one place, I'm really not confident in that assumption?

cout << (p1-t1) << endl; 

So then we move onto the cout, now my take on this is, I'm subtracting the position of p1 from the position of t1, where p1 was positioned by the while and t1 points to the first position in the array. again I could be completely wrong I'm only learning about pointers so please bear this in mind if I'm wrong in my assumptions.

13
  • 1
    @Johntk: main should return an int. Some systems expect that main returns 0 if and only if program finished his work succesfully. When main returns void, when system will try to read the exit status, gets garbage. Commented Oct 14, 2014 at 17:33
  • 5
    These lines of code are demonstrating that the programmer is incompetent, or producing joke code. If it got into your code base, it is demonstrating that your review process for your code base needs to be reworked. Oh, and it prints "5\n3\n", but that isn't the important part. Commented Oct 14, 2014 at 19:01
  • 4
    The assignment, I think, is just to make us think and to research, we are not being told or taught to code like this. Commented Oct 14, 2014 at 22:51
  • 4
    I think, teachers should make a disclaimer before classes, "If you ever wrote code like that I would fire you, but for now, we shall be the compiler, and the executor." and ask to remind it whenever students dare to seek external help. The code seems valid and not invoking UB (don't hang me if i'm wrong). IMO it's just an academical question to bend some minds in a try to understand black magic incantations. Err... statements. Commented Oct 15, 2014 at 0:16
  • 2
    @JamesKanze - but even if I never write code like that, I still have to be able to read it, because someone else will write it. "I don't know what this code does" is not an acceptable answer when the application crashes. Commented Oct 15, 2014 at 14:09

6 Answers 6

46

The while loop is actually quite horrid. I've never seen code like this in real life, and would declare any programmer doing it in real life as mad. We need to go through this step by step:

while (condition); 

We have here a while statement with an empty statement (the ";" alone is an empty statement). The condition is evaluated, and if it is true, then the statement is executed (which does nothing because it is an empty statement) and we start all over again. In other words, the condition is evaluated repeatedly until it is false.

condition1 || condition2 

This is an "or" statement. The first condition is evaluated. If it is true, then the second condition is not evaluated and the result is "true". If it is false, then the second condition is evaluated, and the result is "true" or "false" accordingly.

while (condition1 || condition2); 

This evaluates the first condition. If it's true we start all over. If it is false, we evaluate the second condition. If that is true, we start all over. If both are false, we exit the loop. Note that the second condition is only evaluated if the first one is false. Now we look at the conditions:

!*p1++ !*p2++ 

This is the same as *(p1++) == 0 and *(p2++) == 0. Each condition increases p1 or p2 after it has been evaluated, no matter what the outcome. Each condition is true if *p1 or *p2 was zero and false otherwise. Now we check what happens at each iteration:

p1 = &t1 [0], p2 = &t2 [0] *p1++ == 0 is true, *p2++ == 0 is never evaluated, p1 = &t1 [1], p2 = &t2 [0]. *p1++ == 0 is true, *p2++ == 0 is never evaluated, p1 = &t1 [2], p2 = &t2 [0]. *p1++ == 0 is false, *p2++ == 0 is true, p1 = &t1 [3], p2 = &t2 [1]. *p1++ == 0 is false, *p2++ == 0 is true, p1 = &t1 [4], p2 = &t2 [2]. *p1++ == 0 is false, *p2++ == 0 is false, p1 = &t1 [5], p2 = &t2 [3]. 

t1 is the same as &t1 [0]. p1 - t1 == &t1 [5] - &t1 [0] == 5. t2 is the same as &t2 [0]. p2 - t2 == &t2 [3] - &t2 [0] == 3.

Sign up to request clarification or add additional context in comments.

9 Comments

Thank you, all the answers here are good, but your last block of code really helped me visualize what was going on.
I'll second @GingerPlusPlus comment. Side effects in a condition are generally not a good idea (although there are some consecrated idioms that use them); conditional side effects are horrible. And using ! to compare with zero, instead of being explicit. More obfuscation.
With rare exceptions (e.g. for parallel structure with inequality comparisons nearby) IMHO one should always write comparisons with zero using !var or bare var, especially when var is a pointer. The rationale is basically the same as why one should never write an explicit == true or == false.
@alex.forencich If you mean that raw pointers should be avoided when possible, sure, but pointers past the end of arrays are quite common and useful; random-access iterators are often simply thin wrappers around raw pointers, and in such a case a container's end() iterator is effectively the same as such a pointer.
The point of an example like this has nothing to do with being good code. It is a great debugging and logic exercise.
|
14

You are correct in you assessment of t1, t2, p1, and p2.

while (!*p1++ || !*p2++); 

I don't like this coding style, as it is easy to assume the programmer placed the semi-colon there by mistake. To indicate that an empty body is truly intended, the empty body should be distinguished in some way (like with a comment, placed on a separate line, or use curly braces instead).

The while enters the body so long as the condition is true. Since this is a logical-or expression, both !*p1++ and !*p2++ must be false before the while loop terminates. This happens when both *p1++ and *p2++ become non-zero. Because logical-or short circuits (the second expression is not evaluated if the first one is true), the progression of the p1 and p2 take on the following at the start of each iteration:

iter p1 *p1 p2 *p2 condition ---- -- --- -- --- --------- 0 &t1[0] 0 &t2[0] 0 !*p1++ is true, !*p2++ not evaluated 1 &t1[1] 0 &t2[0] 0 !*p1++ is true, !*p2++ not evaluated 2 &t1[2] 1 &t2[0] 0 !*p1++ is false, !*p2++ is true 3 &t1[3] 1 &t2[1] 0 !*p1++ is false, !*p2++ is true 4 &t1[4] 1 &t2[2] 1 !*p1++ is false, !*p2++ is false 

Since each iteration uses post-increment, p1 ends with the value &t1[5], and p2 ends with the value &t2[3].

Pointer subtraction within the same array measures the distance between the two pointers in terms of number of array elements. An array name used in most expressions will decay to the value equal to the pointer to its first element. So t1 decays to &t1[0], and t2 decays to &t2[0].

Thus:

p1 - t1 => 5 p2 - t2 => 3 

1 Comment

+1 for "Pointer subtraction within the same array measures the distance between the two pointers in terms of number of array elements" which wasn't really clear in other answers
13

The key thing to note here is how the expression (a || b) is evaluated. First, the expression a is evaluated. If a returns true, b is not evaluated since OR of anything with True is True. This is called short-circuiting.

It helps to augment the code in the following manner -

int main(void){ int t1[] = {0,0,1,1,1}, t2[] = {0,0,1,1,1}; int *p1 = t1, *p2 = t2; cout << *p1 << " " << *p2 << endl; cout << p1 << " " << p2 << endl; while (!*p1++ || !*p2++) { cout << *p1 << " " << *p2 << endl; cout << p1 << " " << p2 << endl; } cout << (p1-t1) << endl; cout << (p2-t2) << endl; return 0; } 

Output:

0 0 0x7fff550709d0 0x7fff550709f0 0 0 0x7fff550709d4 0x7fff550709f0 1 0 0x7fff550709d8 0x7fff550709f0 1 0 0x7fff550709dc 0x7fff550709f4 1 1 0x7fff550709e0 0x7fff550709f8 5 // Final p1 - t1 3 // Final p2 - t2 

!*p1++ is equivalent to (!(*(p1++)). This is the post-increment operator. It increments the pointer but returns the old value (before the increment).

The expression in the loop is evaluated 5 times.

  1. In the first iteration, p1 is incremented. Since the current value of *p1 (before incrementing) is 0, a ! of 0 returns 1. Due to short-circuiting, the rest of the expression is not evaluated. Thus only p1 gets incremented.

  2. Same thing happens in the next loop.

Now, we have p1 = t1 + 2 indices, and p2 = t2.

  1. In the third iteration, current value of *p1 is no longer 0. Thus both p1 and p2 are incremented.

  2. Same thing happens in the fourth iteration.

Note that in the first four iterations, either p1 or p2 points to a 0 - so the not of either the left side or the right side is True and hence the while loop continues.

  1. In the fifth iteration, both p1 and p2 are incremented, but since neither points to a 0 value, the loop exits.

Thus p1 is incremented 5 times, and p2 is incremented 3 times.

Summarizing - p1 - t1 will contain 1 + the number of 0s appearing continuously in the beginning of t1 and t2 (2 + 2 + 1). p2 - t2 will evaluate to 1 + number of 0s appearing continuously in the beginning of t2 (2 + 1).

Comments

6

First:

while (!*p1++ || !*p2++); 

That means while the contents of p1 is 0 keep looping adding 1 to p1 each time until it becomes non-zero. Thereafter while the contents of p2 is 0 keep looping adding 1 to both p1 and p2 each time. If at any time the content of p1 become 0 again the logic repeats (I know this is confusing).

Basically in a while(first || second) style test the second part is only tested if the first part fails. And the pointer gets incremented regardless if the test passes or fails.

Your assumption about (p1-t1) is correct. That calculation gives you the number of integers between t1 and p1 (because they are int pointers). Because t1 is the beginning of the array the calculation actually gives you the index (offset) into the array that p1 is pointing at.

NOTE #1: If p1 and t1 were char pointers then subtracting them would give you the number of characters between them. If they were float pointers then subtracting them would give you the number of floats etc... Pointer arithmetic adds and subtracts in units of the data type they are pointing to.

NOTE #2: Strictly speaking t1 is an array type. It collapses to a pointer when you use it in a pointer context. For example in pointer arithmetic or when you assign it to a pointer variable. If that confuses you, don't worry, mostly it just works as a pointer because the compiler makes the conversion automatically whenever it is implied by the context.

6 Comments

Saying that t1 is an int pointer is incorrect and confusing. It can be implicitly converted to an int pointer, which is not the same thing.
@Slava I want to keep the explanation simple so I will add a note to give a fuller explanation..
Also your statement "keep looping adding 1 to both p1 and p2 each time." is incorrect as well, you forget about short circuiting
@Slava Yes I am still fighting with how to word that properly.
"... keep looping adding 1 to both p1 and p2 each time" - not really. It scans with p1, looking for nonzero values in the t1 array. For each such nonzero value, it skips it together with one zero value in t2. The end result is something like "count leading zeros in t2; skip that number of nonzero elements in t1, together with any zeros in t1".
|
2

As far as question is what would this print on console , answer is 0 0 before you removes ; at the end of while loop.

What's the significance of this loop?

First you are using OR that means if either value pointed to by p1 or p2 is 0 block would be executed. So, till p1 points to 3rd element (p1-t1) will give you number of elements crossed in t1 while (p2-t2) will be 0 as (p1-t1) will return true so second condition will not be checked. When p1 points to 1 then it will start incrementing p2 till it points to 3rd element of t2 and there's the end.

This is all this assignment has for you I believe.

Comments

2

This relation can help you better understand the conditions within the while loop:

arr[ i ] == * ( arr + i ) 

When doing pointer subtraction (if pointers are of the same type), the result is the distance (in array elements) between the two elements.

Assume that p1 and p2 are both pointers of type T*. Then, the value computed is:

( p2 - p1 ) == ( addr( p2 ) - addr( p1 ) ) / sizeof( T ) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.