
A static_cast is not always just a pointer adjustment - Tomte
https://blogs.msdn.microsoft.com/oldnewthing/20160224-00/?p=93081
======
pcwalton
Multiple inheritance in its many forms (including interfaces as found in
Java/C#/Golang) always makes casts interesting, even sometimes when methods
aren't involved.

Somewhat related stuff that might be interesting:

dynamic_cast in C++: [http://mentorembedded.github.io/cxx-
abi/abi.html#rtti](http://mentorembedded.github.io/cxx-abi/abi.html#rtti)

Golang's interface casts (also used by Swift I believe):
[http://research.swtch.com/interfaces](http://research.swtch.com/interfaces)

Speculation for interface casts in Java:
[https://wiki.openjdk.java.net/display/HotSpot/PerformanceTec...](https://wiki.openjdk.java.net/display/HotSpot/PerformanceTechniques)

------
graycat
I tried, but I never could understand _cast_ in C or anywhere else. On the
other hand, for decades I've seen, seen documentation for, and used several
data type conversions among common elementary data types -- binary, 2's
complement integers, decimal, base 2 floating point, base 16 floating point,
precision differences, character strings, etc.

So, on the one hand there is _cast_ , and on the other hand there is data type
conversions. _Cast_ is usually documented as just a _cast_ where the meaning
of that word is not in any dictionary and is not well explained otherwise. So,
with a _cast_ , often are getting a data conversion of some kind, but are
usually missing the documentation to know just what.

My best explanation: C wanted some cases of _strong typing_ , e.g., so that
pointers could have data types and, thus, the relatively heavy use of pointers
would cause less trouble. But a _cast_ was an instance of overriding the usual
strong typing. So, really, _cast_ was presented as a way to override strong
typing, but the issue of just how the data conversions would be done was left
poorly or undocumented.

When I do a data conversion, I'm fully aware of the violation of strong typing
-- I know that and am not concerned. But what I am concerned about is just how
the data conversions are done, and in C, etc., I don't see the documentation
to tell me. In strong contrast, PL/I was very careful to document how the data
conversions would be done.

So, each time I see _cast_ without details of how the data conversions are
done, I'm ready to scream.

Am I the only one who cares about the details of the data conversions than the
violation of strong typing? Again, to me, the main issue is the data
conversion, not the strong typing.

~~~
khedoros
Cast (according to the C99 standard, section 6.3, linked at bottom): A
construction formed by preceding an expression by a parenthesized type name,
converting the value of the expression to the named type.

So in C, casting is a language syntax that provides and explicit way to
specify type conversion (rather than the implicit conversion that happens in
cases like promotion). Most type conversions

Section 6.2.5 contains the defined type conversions, but most of what I'd
consider edge cases are "implementation-defined" or have "undefined behavior".

Does any of this bother me? No, not really. No more than it bothers me that
there isn't a single, authoritative, and clear definition of what constitutes
"strong typing" and which operations are a "violation" of it.

(Linked from Wikipedia as being the C99 standard "effectively available for
free"): [http://www.open-
std.org/jtc1/sc22/WG14/www/docs/n1256.pdf](http://www.open-
std.org/jtc1/sc22/WG14/www/docs/n1256.pdf)

~~~
graycat
Thanks for the reference.

Gee, that's a long way ahead of what I got out of reading K&R!

In

> So in C, casting is a language syntax

right. So, _cast_ is just syntax but short on _semantics_ , that is, how the
conversion is actually done so that we can know the results, that is, if you
will, what the heck happens.

In

6.3.1.5 Real floating types

is

> If the value being converted is in the range of values that can be
> represented but cannot be represented exactly, the result is either the
> nearest higher or nearest lower representable value, chosen in an
> implementation-defined manner. If the value being converted is outside the
> range of values that can be represented, the behavior is undefined.

So, yes, this is an example of where I'd be concerned.

On "strong typing", I always thought that the concept was poorly defined and
described but never screamed bloody murder about it if only because it didn't
directly affect what my software would do. I used "strong typing" in my post
if only because it seems to be what the C community likes to do, and when in
Rome do as Romans do. Or, maybe strong typing is better than _weak typing_ or
_typeless_ \-- okay by me.

------
catnaroek
Reminds me of point 3 in [http://www.sebastiansylvan.com/post/language-design-
deal-bre...](http://www.sebastiansylvan.com/post/language-design-deal-
breakers/): “You have to make sure that nullable pointers _cannot be
dereferenced_. (...) Having every few statements potentially trigger a runtime
crash is an expensive price to pay... _for nothing!_ ” [emphasis in the
original] Once you have proper non-nullable pointer types, `static_cast` is a
single pointer bump once again.

~~~
Animats
This is why C++ code should use references more and pointers less. How's that
working out in the era of move semantics?

Bjarne has said that "this" should have been a reference; making it a pointer
was a mistake. But the early design of constructors pointed the language in
the wrong direction.

At least you can't assign to "this" any more. You can still assign to "*this",
but probably shouldn't.

~~~
nikbackm
> This is why C++ code should use references more and pointers less. How's
> that working out in the era of move semantics?

The point of move semantics is to enable use of values instead of pointers.
That's even better/safer than using references.

~~~
catnaroek
Unfortunately, move semantics in C++ is broken, and will remain so until the
type system finally understands that `std::move` is supposed to invalidate the
original object. But, this being C++, I won't hold my breath.

~~~
Animats
Trying to fix this with the type system doesn't really work. Tracking the
changing attributes along a path of control flow with the type system requires
an insanely complicated type system.

Rust's borrow checker understands invalidation via move, and lots of other
changes in variable state which occur along execution paths. That seems the
way to go. But it has to be integrated into the language design. As a
backwards compatible bolt-on to C++, it just won't work.

(It's been tried. I tried once, about 10 years ago, and gave up. I talked to
the designers of Rust, and they tried and gave up. There have been other
attempts to get C++ ownership under compile time checking, and the result is
either a new incompatible C++ variant or something that leaks like a sieve.
Ownership needs to be a fundamental language concept independent of type)

~~~
steveklabnik
Have you seen the Core Gudelines and the GSL? Not as powerful as Rust, but
very interesting.

~~~
catnaroek
The C++ Core Guidelines aren't always mechanically enforceable. Experience
shows that unenforceable rules _will_ be broken. After all, what's undefined
behavior, if not an unenforceable “don't do this” rule?

~~~
steveklabnik
Also, they don't deal with concurrency currently, either.

------
jrziviani
It's possible to have 'this' == NULL. Consider the code:

    
    
      #include <iostream>
    
      using namespace std;
    
      class myclass
      {
          public:
              int sum(int a, int b)
              {
                  cout << this << endl;
                  return a + b;
              }
      };
    
      int main()
      {
          cout << ((myclass*)0)->sum(10, 11) << endl;
          return 0;
      }
    

this will print: 0 21

And it's not a null pointer dereference because in C++ it's the function
responsible to know the class its belongs to. It means that this code:

    
    
      ((myclass*)0)->sum(10, 11)
    

should become:

    
    
      sum(0 /*this*/, 10, 11)
    

and not

    
    
      0->sum(10, 11)
    

It can be UB in the C++ specification, but considering the systemv abi, 'this'
is just a parameter that will be pushed in the call stack before calling
'sum'.

~~~
wbkang
What happens if sum is a virtual function? Or does it not matter? I am not too
familiar with c++ casting magic.

~~~
stormbrew
Most likely you'll get a segfault when it tries to read from page 0 to get to
the class' vtable (which is where the references to the virtual functions
live).

------
known
static_cast is used mainly to "reuse" existing code base

~~~
ant6n
I use it mostly to switch between numerical types like double, float, int,
uint32_t, char, size_t.

------
anon4
Jesus Fucking Christ, now we're exploiting undefined behaviour to remove one
compare instruction? You don't even need the jump, just use a conditional
move. Just..

    
    
        test rax, rax
        add rax, $offset
        cmovz rax, 0
    
    

Or keep the damn jump and expect the cpu to profile it correctly.

Sorry for sounding angry, but this kind of thing isn't what makes my programs
faster, this kind of thing is what makes it harder to write correct programs.

Edit: clang does exactly this at -O1:
[https://godbolt.org/g/o6gh0M](https://godbolt.org/g/o6gh0M)

~~~
catnaroek
That you need to invoke undefined behavior to remove the compare instruction
is only the symptom. The actual disease is nullable pointers themselves, which
create the need for a compare instruction at every pointer dereference in the
first place.

~~~
titzer
No. Java has nullable pointers and almost all JVMs implement them with a
pointer to address 0 and unmap the first couple of pages of the address space.
That will cause a fault, leading to a signal, which is then handled to
materialize and throw the exception. No compare. Just naked loads/stores with
fixed offsets. The disease is multiple (implementation) inheritance.

~~~
catnaroek
> No. Java has nullable pointers and almost all JVMs implement them with a
> pointer to address 0 and unmap the first couple of pages of the address
> space.

This is just plain ugly. Why should the normal operation of a program written
in a _high-level_ language trigger page faults? But, leaving aesthetic
concerns aside, I'm not even sure it works. How do you guarantee that the OS
won't give you back the same pages you unmapped when you try to map new pages
(say, to grow the heap)?

~~~
titzer
> This is just plain ugly.

Yeah. Unfortunately the only really efficient protection mechanisms that
modern processors have is virtual memory. C++ programs generally unmap the
first few pages for exactly the same reason; to catch nullptr derefs.

> Why should the normal operation of a program written in a high-level
> language trigger page faults?

NullPointerExceptions are not considered normal operation. They are safety
violations that have a controlled, well-defined semantics. BTW page faults
happen all the time; the OS transparently handles them and maps in new pages
as necessary. The problem you are referring occurs when a page fault happens
and the OS knows there is no mapping for those addresses.

> How do you guarantee that the OS won't give you back the same pages you
> unmapped

Because the mapping is for an address space range (e.g. 0-N), and the OS does
not overlap new request with existing mappings unless specified in the
request.

~~~
catnaroek
> and the OS does not overlap new request with existing mappings unless
> specified in the request.

Ah, so by “unmap”, you actually something like POSIX's `mprotect()`, rather
than `munmap()`?

~~~
titzer
Sorry, yes. You can do this just through segment declarations in both ELF and
MachO binary formats, to prevent anything getting accidentally mapped there
before startup.

