Do not confuse undefined behaviour with undefined result



  • 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). Casting int * to bool * 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 * to bool * can crash.

    Nope, the result of casting an int* to a bool* is unspecified, but the cast itself does not lead to undefined behaviour. The standard even guarantees that casting the resulting value back to int* 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_ptr would, in fact, point to an object of type bool (i.e, it would be a bool* converted to an int* ) 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 * to bool * and then accessing and using this bool can crash».



  • SAn schrieb:

    I wanted to say that «casting int * to bool * 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 false as zero and true as one (and this is the only valid value for true ). So, it can use bitwise arithmetic for boolean operations.

    If you cast int to bool then compiler will compare it to zero and assign zero or one to bool variable 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 return in else clause 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 else clause since it can skip it by jz/jnz assembly instruction)

    Both if s will go by "true" branch, and function will not return anything.

    So, reading the int value as bool value 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 float values. 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 to if statements (decision trees). We know than positive float s can be compared when they loaded as int s (pointer casting!). So, minor modification of code (casting float data pointer to int pointer) leads to 30% performance improvement! Why we should not do this if everything just works? 🙂


  • Mod

    SAn schrieb:

    Here is the example. We have image consisting of pixels having positive float values. 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 to if statements (decision trees). We know than positive float s can be compared when they loaded as int s (pointer casting!). So, minor modification of code (casting float data pointer to int pointer) 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 float values. 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 to if statements (decision trees). We know than positive float s can be compared when they loaded as int s (pointer casting!). So, minor modification of code (casting float data pointer to int pointer) 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...


  • Mod

    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.



  • hustbaer if you was taller and less kindhearted you will be basketball player or boxer, not C++ programmer.


Anmelden zum Antworten