Small C++ Test.

  • Thread starter Thread starter L. Spiro
  • Start date Start date
Status
Not open for further replies.
L

L. Spiro

Guest
Not too hard, but a good tester of just how intimate you are with the language.

Obviously the solutions to all of these problems can be found by simply compiling the code and viewing the results, but that defeats the purpose entirely.

Try to see how many of these you know right off the top of your head.

These are all my own original questions, by the way.


DISCLAIMER: THESE QUESTIONS ARE NOT INTENDED TO BE EXAMPLES OF QUALITY CODE.  IN FACT, THE ANSWERS TO MOST THESE QUESTIONS DEMONSTRATE EXACTLY WHY YOU SHOULD NOT CODE THIS WAY.


1:
Code: [Select]
Code:
int I = 30;int J = ((I++) + I++);
What is the value of J?

2:
Code: [Select]
Code:
int I = 0;int J = 0;if ( I++ == 1 && I++ == 2 ) { J = 1; }
What is the value of I after this entire code segment is processed?  Why is this significant?

3:
Code: [Select]
Code:
int I = 0;int J = ( I++ == 1 || ++I == 1 ) ? ++I : I++;
What is the value of J?

4:
Code: [Select]
Code:
(int *)6 - (int *)2
What is the result of this expression and why?


L. Spiro
 
Mmm...  So I answered them all and then checked them before posting.  :-P  I got them all except 4.

1.
I was expecting 61 (30 + 31) but VS2005 gave me 60.
It must be taking some shortcut?
I was so expecting 61 that I tried it on a different compiler (Metrowerks CodeWarrior) and then I got 61 as the result.

2.
Value of I = 1
(It short-circuited out of the check because the one of the left was false)

3.
Value of J = 2

4.
Silly pointer math.
I'm at a loss to explain this one, it's a C++ism that I'm not familiar with.
 
I remember when I was discovering the power of pre- and post- incrementation. It took me a while before I realized that you can rewrite the line moving all the pre-incr. before the executed line and all the post-incr. after the line. That's why Aaron's reply to the 1st one surprised me...

And the 4th question doesn't have one answer. It depends on the system you run it on (
size of integer
).

dziugo
 
On that one...

I see how subtracting a number from a pointer would be different, depending on the size of an integer (or whatever data type).  For example...

(int*)10 - 2;
is kind of like
10 - (2 * sizeof(int));

I guess I haven't used pointer subtraction like this before, though, it's giving me a result of 1 (whether I am working in 32-bit 0x00000001 or 64-bit 0x0000000000000001) and I don't really know why.  :-P
 
Number 4 is the main one that so far no one, including my boss (7-year Ubisoft employee), has gotten.
It simply never happens in real coding.
But for the sake of simplicity let’s assume a standard 32-bit integer is being used.


According to the actual ANSI C specifications, Aaron, your CodeWarrior compiler is wrong.


5:
Code: [Select]
Code:
int I = 2;I = I++;
What is the value of I?

6:
Code: [Select]
Code:
int I = 5;int J = 0;if ( I++ == ++I ) { J = 1; }
What is the value of J and why?


L. Spiro
 
Wow, that's some REALLY bad code!  Anyway, I'll take a guess at them and then see what GCC produces . . .


1. I'm thinking it'll be 61 . . . this, though, might be one of those things that depends on the compiler (could end up being 62).  Even though i++ is *supposed* to be POST increment.

2.  Well, either way, I will be 2.  Now, whether or not J is assigned 1 again probably depends on the compiler.  Even though technically it SHOULDN'T be.

3.  Oh, this is disgusting . . . either it'll be 3 or 2.  It's *supposed* to be 2, though.

4. My god . . . I would imagine this thing would result in some kind of a bus error depending on its context.  I . . . don't even know what to say about this . . . All I know is, if I saw anyone seriously writing code like this I would smack them.  HARD.

5.  Should be 3, either way.

6.  Holy shitsticks, batman!  Um . . . geez.  I'm gonna go with . . . 0 on  this one.  What *probably* should be happening there is a comparison of 5 to 7 (which are of course not equal).


                        ------------------EDIT------------------

Here's what GCC/G++ produced:


1. Both produced 60.

2. Both produced 1.  You know why?  Short-circuit evaluation.  I realized this as soon as I saw the answer.  Silly me for thinking 2 above.

3. Both produced 2.


4. g++:  couldn't get this to compile.  I tried a variety of things.
With gcc I used this code:

Code: [Select]
Code:
#include <stdio.h>int main() { int *p;  p = (int *)6 - (int *)2; printf("%d", &p); return 0;} // end int main()


and got a bullshit value.  So somehow, the app could actually de-reference that pointer and give me the "value" in the address


I changed the
Code: [Select]
Code:
printf("%d", &p);
to
Code: [Select]
Code:
printf("%d", p);

and got a 1 . . . go figure.  I really have no fucking clue where that came from.  If I had to guess, I'd say it was printing the value in "words" and since the address 4 is "word 1" on an x86, this is what it gave me.


Remember kids:  DON'T TRY THIS AT HOME!!


5. Both of them produced 2.  Again, silly me.  This one VERY MUCH depends on the compiler writer.  Should the assignment of I happen *AFTER* the ++ or before it.  On one hand, I could get assigned 2 and THEN incremented.  On the *OTHER* hand, the 2 could be stored, I incremented and THEN I assigned 2.  There is a good saying for anyone who writes code like this:  "You get what you deserve."

6. Both of them produced 0.
 
No. 4

(int*) 6 returns 6
(int*) 2 returns 2

(int*)6 - (int*) 2
is really
(6 - 2) / sizeof(integer)
returning 1
due to the pointer arithmetic involved.

So if you did (int*)10 - (int*)2 you would get 2. [( 10 - 8 ) / 4]

In case you're interested, most compilers will implement it as a double shift right instead of a divide by 4.
 
[( 10 - 8 ) / 4]

should be
[( 10 - 2 ) / 4] cause  10 - 8 = 2 and 2 / 4 = 1/2 or 0.5 and not the 2 you were saying. right?
 
Questons: 1 - 4

1. 60 - The int I doesn't increment until after the assignment is given.
2. 00 - For the same reason above.
3. 02 - Anyone who actually uses that should be shot ...then kicked ...then run over with a truck.
4. Pointer arithmetic? Wouldn't that depend on the compiler that’s used? Funny thing is: I don’t think other data types have this problem.


What might confuse the vets? (excluding the
rarely used prefix of ++
)

Questons: (4.5) - 6

4.5 32-bit? Nope, still too lazy to wip out that old txtbook.
5. 03 - Ooo, tricky. :wink:
6. 00 - If I is 1 above itself now or after? Ok?
 
What might confuse the vets?
Well your answer for #2 is incorrect, but its’s because you misread the actual question rather than lack of understanding the inner workings of the problem.


The SaiNt, you explain how the value is obtained, but the second part of the question is why?  What is the significance of the value that is returned from that expression?


7:
Code: [Select]
Code:
int I = 0;g_mMyStructArray[I++].sAnotherStruct[++I].iInt = (I++ == 0) ? I++ : -1;
Specify which iInt (g_mMyStructArray[??].sAnotherStruct[??].iInt) will have its value assigned and why.


L. Spiro
 
1: 61
2: i = 1 because it jumps out of the comparison after first false argument
3: j = 2
4: no idea. it could be 1 because of sizeof( int ) = 4 in these days.
5: i = 3. i would say it gets assigned an afterwards increased.
6: no idea but i hope for god's sake that I is increased before comparison so J = 1
7: i refuse to continue !


my comment:
dumb constructions ! allways use parenthesis and readable form of formulaes to avoid misunderstanding and these kinds of questions !
I am a 6-year veteran ;) and I never use these constructs because they are leading only to misunderstandings, not to faster code. Easy code readability is important to me.  That's why such knowledge should not be important.


I have one for you too. What is result of the expression ? Is it int or float ?

 ( 1 < 2 ) ? 3 : 4.0
 
These questions are very clearly not intended to indicate superiority in coding style.
For very clear reasons, you want to avoid coding this way.
But the test is actually to ensure actual understanding of operator precedence and order of evaluation, as such knowledge is valuable in normal code we write.

For example, the key to #2 is
to understand the importance of short-circuit code, because it specifically allows the following to be safe:

Code: [Select]
Code:
if ( piPointerToInt != NULL && *piPointerToInt  == 3 ) { /* Do something. */ }

This is good coding practice if the people reading/writing the code understand this concept, while new coders may steer away from this, just from lack of understanding.


The key to #7 is
understanding completely the order of evaluation.
It is important to know whether the left or right side of assignment expressions is evaluated first, for a start.


#4 is intended
to awaken programmers to the fact that pointer subtraction actually exists, and that the result is an int (not a pointer of any kind) that represents the difference in indices between the two addresses, given the specified type.


I am a 6-year veteran  and I never use these constructs because they are leading only to misunderstandings
And you are absolutely correct in deciding not to use many of these constructions.
But knowledge is always important.


I have one for you too. What is result of the expression ? Is it int or float ?

( 1 < 2 ) ? 3 : 4.0

Coincidentally, I just wrote that part of my own programming language last night, so unfortunately I already covered this on my own too (but did forget to ask it).
It is luck that I have actually written a C compiler, and have been forced to examine the results of all the questions posted here, including this one.


IF THE CONDITIONAL EXPRESSION WAS NOT CONSTANT:
As per the ANSI C++ standard, both values will be promoted based off the same rules that are used when and int and a double are multiplied, added, subtracted, etc.
If either operand is a double, both are promoted to doubles.
If either are floats, both become floats.
If either is an unsigned long, both become unsigned longs.
If either is an unsigned int, both become unsigned ints.
Finally, if none of the above has yet been matched, both sides become regular ints.

This changes per compiler based on sizes of int, etc., and whether or not int64 is supported, but in all cases double takes the cake.

Thus, the result of the expression would be a double in this case, then cast to whatever type is being assigned by this expression.


BUT SINCE THE CONDITIONAL EXPRESSION IS CONSTANT (1 is always less than 2):
The result will be an int.  The conditional expression and the third operand (4.0) will be completely ignored, and the expression is exactly equivalent to just typing “3” in the code instead.


L. Spiro
 
to L.Spiro's answer:

Try checking it in your compiler. Mine (MSVC 2005) does think that 3.0 double is an answer. I have checked it with this kind of test:

void fn( double d ) {  printf( "double %0.3f", d );  }
void fn( int d )  {  printf( "int %i", d );  }

int _tmain(int argc, _TCHAR* argv[])
{
   fn( ( 1 < 2 ) ? 3 : 4.0 );
   return 0;
}

Result printed on screen was: "double 3.000"
 
I get the same result but it may be a Microsoft thing.
They might be choosing their overload based off the same promotion rules I explained before, because neither overload is specifically better than the other except by the precision of the type.

The ANSI C++ definition implies that the 3 should be the immediate return with no code produced, and that its type should be retained, but I would like to see what other compilers return.


L. Spiro
 
Actually, the answer to (1), (6) and (7) is: completely undefined. The compiler could return 42 every time, shoot your pet dog, or cause the computer to blow up: all would be valid responses according to the C++ specification.


So for (7), it would be completely valid to say that every single entry in the array will have its value assigned. Even if there were 10 million of them. Undefined behaviour means the compiler can do anything. Of course, a more useful answer would be to point that the code is not really valid according to the specification.

See: http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.15 for an explanation why.

Also, a key point for (4) that I'd want anybody playing who-understands-the-language-the-most games to bring up would be: what if 0x6 and 0x2 aren't actually valid values for pointers to integers? Since you never dereference them, obviously you aren't actually going to trigger a memory violation, but it's not an irrelevant point if you're going to introduce pointers. After all, technically the difference in indices between the two addresses could be "undefined" since on some systems, neither contains a value that could actually point to an integer. I suspect the specification doesn't actually cover this behaviour, however.
 
...your answer for #2 is incorrect.
Of course! {slaps forehead}
 I must have been looking at J. I=2 because I++ (in both forms) increments I before the next statement is occurred, regardless of how it's used.
Thanks for the heads up.
Edit:
Never mind. After finally compiling it, I think I'm the one who just short-circuited. LOL.
Cool experiment.

...very clearly not intended to indicate superiority in coding style.
Phew, lol. I was hesitant to say anything. :wink:

#7

...[0]...[1].iInt
This is because the first I is not incremented until after the statement is run (I think) and the second I is incremented immediately. The resulting value of I, after the entire statement, should be 2.


Actually, the answer to (1), (6) and (7) is: completely undefined.
Com'on! This is just for fun... :P I hope nobody (in their right mind) would actually use such statements.
 
The 7th one is... Weird... I thought that I got why it does it that way, but then I compiled something like:Code: [Select]
Code:
int I = 0;struct1[++I].struct2[++I].struct3[++I].struct4[++I].struct5[++I].iInt = 1;
And... See by yourself. I don't expect an answer with explanation :P

dziugo
 
Actually, the answer to (1), (6) and (7) is: completely undefined.
Com'on! This is just for fun... :P I hope nobody (in their right mind) would actually use such statements.

Sure, just remember that what happens is totally compiler dependent
in situations like that: you can't even really guess what will happen when you run the code, because even different versions of the same compiler might do different things ... or, worst of all, the same compiler might do different things depending on when and where you use it...
 
Forgive my noobishness, but I don't quite understand #2. I understand it has something to do with short-circuit evaluation, but...

Code: [Select]
Code:
int I = 0, J = 0;if ( I++ == 1 && I++ == 2)J = 1;


Why doesn't that work?

Would it have to be:

if (I++ == 1 & I++ == 2)
J = 1;
 
To RPGillespie:
In the left side of the &&, I is compared as 0 (and increased afterwards) so the condition fails immediately and the right side is not evaluated at all.
This is what people mean by “short-circuit” method, and the C/C++ specification dictates that this is the correct behavior for two reasons.
1: Saves time; the right side does not need to be evaluated since the statement can’t be true anyway.
2: Allows you to check a pointer for NULL on the left side, then use indirection on the pointer on the right side.  If the pointer is NULL, the indirection on the left side will not cause an access violation because it isn’t evaluated at all.


To ficedula:
Ding ding ding ding!  Congratulations on being the first to note that some of these are undefined.   :D  There are two intentions here:
1: Bring about more awareness towards the types of things you should avoid while coding because the results may not be consistent across compilers.
2: Have a bit of fun by logically deducing what should happen if the ANSI C/C++ specification is not obscured by implementation.  The ANSI C++ specification says explicitly that values modified twice between sequence points yield undefined results, however the actual result can be logically deduced.  Luckily, however, because of the “undefined”/abstract nature of some of these problems, there are multiple correct deductions.


As for your reply to #4:
1: Sound spiteful much?  It’s not a who-understands-the-language-the-most game.
2: For all intents and purposes, there is no need to explain that the int pointers aren’t valid addresses; that much is clear at first glance.  But you are correct to deduce that the specifications don’t specifically define that the validity of constant pointers be verified at all during compilation (in fact doing so may lead to erroneous output across systems), so the result of constants 6 and 2 are just as valid as constants 0x100006 and 0x100002 (and both yield the same result).


And lastly, #7 is not undefined.
Take a second look at the rules for sequence points and order of evaluation.

The result seems undefined because of the two array indices (don’t let the other two I++ expressions mislead you; that’s the trick to this one), however the ANSI C++ specifications do dictate how to handle this situation (that does not mean every compiler will handle it correctly, as we have already seen some compilers as handling other defined expressions incorrectly).
Of course, this should be avoided at all costs, but tinkering with these problems is just for fun.


L. Spiro
 
Status
Not open for further replies.
Back
Top