Do not confuse undefined behaviour with undefined result
-
I realized that many C++ programmers have misconception about undefined behaviour, thinking of it as just «undefined result». As far as I know there is no such thing as undefined result in C++ standard; only undefined behaviour.
Here is good example we found during debug session. It illustrates the difference between undefined result and undefined behaviour well.
//Function specification: will return 1 for *int_ptr != 0 and 0 for *int_ptr == 0. int_ptr points to value in range 0..9. int someFunc(int *int_ptr) { //bool will take not more than 4 bytes. So, I can just point bool* to the memory location where int is stored. //I have heard that casting pointers is somehow undefined, but I do not really care //because I am clever and know that false is zero and true is not zero, and value in range 0..9 //is just single byte, so even if bool is single byte everything will be good etc... bool *bool_ptr = (bool *)int_ptr; bool flag = *bool_ptr; //I really need boolean value, not pointer if(flag) return 1; //flag is true flag = !flag; //If it was false, after inversion it will be certainly true if(flag) return 0; //Will always return 0 here }The worst thing programmer expects here is that the function will return 1 instead of 0 or vice versa due to undefined result of pointer casting. But not the core dump we got! Moreover, the casting itself works successfully and even some other code after it is executed. Moreover, the core dump appears only in some cases...
Do you need an explanation of this case or you want to think yourselves?

-
Is this supposed to be a question? Your example yields to undefined behavior by the way. A good example where the result is not defined but the behavior is, is this:
bool is_small_endian() { const int i = 1; return *reinterpret_cast<const char*>(&i) != 0; }Still, I'm not really sure what you want to say..
-
Well, the standard actually does distinguish between undefined and unspecified behaviour. The result of the pointer conversion, for example, is unspecified, i.e., there is some result, but what it is exactly depends on the implementation. Accessing the underlying object through the cast pointer value, on the other hand, leads to undefined behaviour...
-
Hard to understand those strange shit. The casts in the first example looks likes pure C-code, isn't it?
-
Clarification:
I presented syntetic example which demonstrates that pointer casting that is widely considered as undefined result (at least for built-in types) is in fact undefined behaviour (even for built-in types). Castingint *tobool *can crash. The function causes core dump at least in one system.I also asked a question: can you identify a reason for crash?
(I really know the reason, just want to give mind-candy for some people)cooky451, you example is undefined behaviour too.
-
SAn schrieb:
Casting
int *tobool *can crash.Nope, the result of casting an
int*to abool*is unspecified, but the cast itself does not lead to undefined behaviour. The standard even guarantees that casting the resulting value back toint*will yield the original value. (§5.2.10/7)Undefined behaviour only arises, when one uses the casted pointer value to access the object. (§3.10/10)
Note that if
int_ptrwould, in fact, point to an object of type bool (i.e, it would be abool*converted to anint*) your sample function above would even exhibit perfectly well defined behaviour according to the standard...SAn schrieb:
cooky451, you example is undefined behaviour too.
No, in this particular case, the result is implementation defined, but there is no undefined behaviour (because he accesses the object through a
const char*, the strict aliasing rule is not violated)...
-
dot
OK, I totally agree with you.I wanted to say that «casting
int *tobool *and then accessing and using this bool can crash».
-
SAn schrieb:
I wanted to say that «casting
int *tobool *and then accessing and using this bool can crash».If the object pointed to is not actually of type
bool, then yes.
-
Casting a pointer does not automatically cast the value to which it points, is that right?
-
Butterbrot schrieb:
Casting a pointer does not automatically cast the value to which it points, is that right?
All you do when casting a pointer, is changing as what the pointed adress of that new pointer should be interpreted when dereferencing that new pointer.
-
[quote="Sone"All you do when casting a pointer, is changing as what the pointed adress of that new pointer should be interpreted when dereferencing that new pointer.[/quote] And sometimes the address as well....
#include <iostream> struct A {int i;}; struct B {double d;}; struct C: A, B {}; int main() { C c; A* pa = &c; B* pb= (B*)((C*)pa); std::cout << (void*) pa << ' ' << (void*) pb << '\n'; };
-
OK, that is how my example may crash.
Suppose that compiler for speed reasons stores
falseas zero andtrueas one (and this is the only valid value fortrue). So, it can use bitwise arithmetic for boolean operations.If you cast
inttoboolthen compiler will compare it to zero and assign zero or one toboolvariable depending on comparison result.The problem that this code may store invalid value (for example, 3) to boolean value, because it casts pointers, not values:
bool *bool_ptr = (bool *)int_ptr; bool flag = *bool_ptr;The negation operator
!may invert the last bit only, so such invalid bool value will always be non-zero independing of negations you apply to it.The compiler may decide that it is better to keep
returninelseclause and rewrite this code:if(flag) return 1; flag = !flag; if(flag) return 0;in following equivalent way:
if(!flag) { if(flag) ; else return 0; } else return 1;(It is better for compiler to have single action in
elseclause since it can skip it by jz/jnz assembly instruction)Both
ifs will go by "true" branch, and function will not return anything.So, reading the
intvalue asboolvalue by means of pointer casting is undefined behaviour, not undefined result as one may think.
-
@SAn
If the Standard says it's UB then it's UB and that's that.
I think for the most part reasoning about why or what could possibly go wrong is contra-productive. Because it gives some people a false sense of security. They see what could go wrong, and think "ah, interesting, so if I make sure that THAT cannot happen, then I'm safe". And still write code that relies on UB constructs.I also asked a question: can you identify a reason for crash?
(I really know the reason, just want to give mind-candy for some people)Proves my point. You say you know THE reason. Which is utter BS. There is not "the reason". Thinking about "the" reason is exactly what leads people to think they're smarter then the Standard. "Oh, I know THE reason, so the rule in the Standard is just for stupid people, but since I'm so smart I can do this anyway."
OK, wait, there is: THE reason is that the Standard says it's UB.Just to give you some other examples why this could cause Bad Things (tm) to happen.
It violates strict aliasing. Sincea access via the alias *bool_ptr is forbidden the compiler doesn't have to consider it, and could decide to not re-load a variable after it has been updated via *bool_ptr.
And then or course there's big-endian vs. little-endian.
And then there's the fact - if I didn't mention it already - that the Standard says it's UB.
-
hustbaer, I totally agree with you. Of course, that was just one of possible reasons. And, of course, that is UB, and we should not waste our time searching what may go wrong.
BUT people around me often rely on undefined behaviour, and when I say them that they should not, they answer: "Hey! The code is tested. Everything works fine!". So, there is not enough for them to just say that this is UB (they really think that they are smarter than standard), so they need a prove that things may really go wrong; some crashing scenario is needed (yes, I got your point about false sense of security).
Such people often say me that writting large programs without UB is contra-productive. And they are right on their own: sometimes we can write small elegant peace of UB code instead of big, ugly, and slow standard-compliant code.
Here is the example. We have image consisting of pixels having positive
floatvalues. We need very fast classifier to segmentate eyes on image (each pixel should be processed). Algorithm uses signs of pixels differences in neighborhood as features, which are then fed toifstatements (decision trees). We know than positivefloats can be compared when they loaded asints (pointer casting!). So, minor modification of code (castingfloatdata pointer tointpointer) leads to 30% performance improvement! Why we should not do this if everything just works?
-
SAn schrieb:
Here is the example. We have image consisting of pixels having positive
floatvalues. We need very fast classifier to segmentate eyes on image (each pixel should be processed). Algorithm uses signs of pixels differences in neighborhood as features, which are then fed toifstatements (decision trees). We know than positivefloats can be compared when they loaded asints (pointer casting!). So, minor modification of code (castingfloatdata pointer tointpointer) leads to 30% performance improvement! Why we should not do this if everything just works?
That is perfectly fine by the C++-Standard. It is implementation defined behavior, of course, but that is very different from undefined behavior. As long as your assumptions about the sizes of integers, floats and the internal representation of both hold, your program will work.
-
SeppJ schrieb:
SAn schrieb:
Here is the example. We have image consisting of pixels having positive
floatvalues. We need very fast classifier to segmentate eyes on image (each pixel should be processed). Algorithm uses signs of pixels differences in neighborhood as features, which are then fed toifstatements (decision trees). We know than positivefloats can be compared when they loaded asints (pointer casting!). So, minor modification of code (castingfloatdata pointer tointpointer) leads to 30% performance improvement! Why we should not do this if everything just works?
That is perfectly fine by the C++-Standard.
IMHO it's not, it violates the strict aliasing rule...
-
dot schrieb:
IMHO it's not, it violates the strict aliasing rule...
Depends how you do it. I don't see why you must violate strict aliasing in this case (of course you can, if you try).
-
Can you give me an example on how you would do this in a well defined manner? The only way I can think of is by using char pointers and probably memcpy()...
-
Here is a joke I came up with:
— What a programmer will do if a cat came up to him on the street and asked how to go to the library?
— It will be undefined behaviour.
-
dot schrieb:
Can you give me an example on how you would do this in a well defined manner? The only way I can think of is by using char pointers and probably memcpy()...
+1
I'd like to see that too.Assuming that the float values have been accessed as floats before the "int hack" runs and/or will be accessed as floats afterwards. And without copying stuff of course.
BTW: A few weeks ago I thought about various low-level algorithms and how nice one can implement them if one ignores strict aliasing. I wish there was some kind of escape-hatch by which one can still do aliasing.
-
SAn schrieb:
BUT people around me often rely on undefined behaviour, and when I say them that they should not, they answer: "Hey! The code is tested. Everything works fine!".
Yeah, know what you mean.
I wish I was taller and less kindhearted. Then I could just punch them in the face. Would be so much easier.