4 ms·
The reason it fails is due to UB. There's not much point in saying that most of it isn't UB since the part that is UB is the part that causes the failure.
by Kranar 5y ago
The reason it fails is due to UB. There's not much point in saying that most of it isn't UB since the part that is UB is the part that causes the failure.
- woodruffw 5y agoThe cast isn't the part that causes the failure (C says that function pointers don't have to be safely representable within `void *` or any other fundamental pointer type, but they are on both x86(-64) and Itanium[1]). The failure happens because the programmer assumed that the code is "self contained" and position-independent, both of which are concepts outside of the C abstract machine. [1]: Function pointers on Itanium are actually fat, but IIRC most compilers hide this by making the "function pointer" point to some kind of thunk instead.
- Kranar 5y agoThis is the type of reasoning that leads people to write insecure code, by trying to outguess the compiler with implementation details that are entirely inapplicable. It suggests that if the code was self-contained or position independent or satisfied any property whatsoever, that using memcpy would be a perfectly fine way to copy it, that is simply untrue, it would never be safe to use memcpy on pointers to functions under any circumstance. It's irrelevant whether you are on x86, Itanium, or any other platform, undefined behavior WILL result in incorrect and invalid program semantics and can not be relied upon to produce consistent results even within the same execution of the same program. There's literally decades worth of people trying to use undefined behavior in clever ways and ultimately failing and yet here we are...
- woodruffw 5y agoI think you're reading too far into my reasoning. I'm not a huge fan of UB, and I mostly write Rust these days to get away from cheekiness like this. My only point was that, for better or worse, UB is not the culprit in this code. C could have well-defined abstract semantics for copying functions or aliasing function pointers through datatype pointers, and this code would still be platform dependent and would still break on different hosts.
- Kranar 5y agoThis is also untrue, if the standard did mandate that pointers to functions could participate in a memcpy and the behavior was as if the instructions of that function were treated as an array of chars, then C++ compilers would be forced to accommodate that implementation regardless of what platform it ran on, Itanium, x86, even a PDP-11 it wouldn't matter. For example it might mean that implementations must tag or otherwise store a dictionary to keep track of addresses to functions so as to differentiate pointers to functions from pointers to objects and produce whatever appropriate behavior is needed to copy said instructions. An implementation could keep an intermediate representation of any function that can potentially be memcpy'd and then translate that representation at runtime when it participates in a memcpy. Whatever the case may be, if the standard mandates certain behavior then an implementation is required to respect it. While not entirely the same, C++ does do something along these lines in certain situations involving pointers to virtual member functions involved in a diamond inheritance structure. Saying that C++ (the code in the article is not C and the two languages differ about the treatment of pointers to functions) could have well defined semantics to handle this is entirely moot... it's about as relevant as saying that a C++ program could run on top of the JVM with a garbage collector [1] and hence eliminate all types of memory errors. Even if a C++ program ran on a platform that had guaranteed garbage collection the fact would still remain that C++ as a language does not have well defined semantics for what happens to a dangling reference and as such a C++ compiler is free to exploit that to make very strong assumptions about the runtime behavior of the program for the sake of generating efficient code. The fact that this is undefined behavior gives a compiler the freedom to perform optimizations under the assumption that runtime behavior will never engender said behavior. Raymond makes use of this property when he discusses COMDAT folding which is a common optimization to elide multiple copies of the same function or to even produce multiple versions of a single function optimized for different scenarios. From the article: "Even without Profile-Guided Optimization, compile-time optimization may inline some or all of a function, so a single function might have multiple copies in memory, each of which has been optimized for its specific call site." This property has nothing to do with x86, or Itanium or PDP-11, it's a purely logical optimization permissible only because of the various forms of undefined behavior in C++ with respect to the treatment of function pointers. [1] http://nestedvm.ibex.org/ http://nestedvm.ibex.org/