I recently watched a CppCon talk by Miro Kenjp on "Non-conforming C++: the Secrets the Committee Is Hiding From You".
At 28:30 (https://youtu.be/IAdLwUXRUvg?t=1710) He states that accessing doubles next to each other was UB and I don't quite understand why. Can someone explain why is this the case?
And if this is the case then surely its UB to access other things this way for example:
int* twoInts = malloc(sizeof(int) * 2);
int secondInt = twoInts[1]; //Undefined behaviour?
In computer programming, undefined behaviour is defined as 'the result of compiling computer code which is not prescribed by the specs of the programming language in which it is written'.
In C the use of any automatic variable before it has been initialized yields undefined behavior, as does integer division by zero, signed integer overflow, indexing an array outside of its defined bounds (see buffer overflow), or null pointer dereferencing.
So, in C/C++ programming, undefined behavior means when the program fails to compile, or it may execute incorrectly, either crashes or generates incorrect results, or when it may fortuitously do exactly what the programmer intended.
Undefined behavior exists mainly to give the compiler freedom to optimize. One thing it allows the compiler to do, for example, is to operate under the assumption that certain things can't happen (without having to first prove that they can't happen, which would often be very difficult or impossible).
undefined behavior - there are no restrictions on the behavior of the program. Examples of undefined behavior are memory accesses outside of array bounds, signed integer overflow, null pointer dereference, modification of the same scalar more than once in an expression without sequence points, access to an object through a pointer...
The following might have undefined behavior due to incorrect pointer alignment: The undefined behavior happens as the pointer is converted. According to C11, if a conversion between two pointer types produces a result that is incorrectly aligned (6.3.2.3), the behavior is undefined.
The undefined behavior happens as the pointer is converted. According to C11, if a conversion between two pointer types produces a result that is incorrectly aligned (6.3.2.3), the behavior is undefined. Here an uint32_t could require alignment of 2 or 4 for example.
Quoting ISO/IEC 9899:201x, section 6.7.3 §2: If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined. [...]
I want to start with a quote from the presentation:
You are not programming against the CPU, you are programming against the abstract machine
You say:
He states that accessing doubles next to each other was UB
But your quote is incomplete. He specifies this very crucial fact:
... unless the objects are part of an array
malloc
is a red herring (and a bag of another set of problems). His code uses new[]
so malloc
is just poisoning the well here.
The specific problem he mentions on his slides is that the double
objects created on the buffer
are created by std::uninitialized_default_construct_n
and this method doesn't create an array of doubles, but instead creates multiple objects that are consecutive in memory. He asserts that in the C++ standard (the abstract machine you are programming against) you can't treat objects as part of an array unless you actually created the array of objects.
The point the author tries to make is that the C++ standard is flawed and there is no strictly conforming way to create a flexible array (pre C++20).
For reference here is the code (reproduced after image):
struct header
{
int size;
byte* buffer;
thing some;
};
constexpr size_t x = ...;
byte* buffer = new byte[x + n * sizeof(double)];
header* p = new (buffer) header{n, buffer};
uninitialized_default_construct_n(
reinterpret_cast<double*>(buffer + x), n);
double* data = reinterpret_cast<double*>(p->buffer + x);
data[0] = data[1] + data[2]; // <-- problem here
// because we never created an array of doubles
There is no guarantee that the variables will be allocated next to each other.
The memory area used by malloc
may be a different region than the memory area for temporary, local, variables.
Local variables may not be stored in memory. They could be stored in registers.
Accessing array elements outside the declared range is undefined behavior.
The array could be allocated at the end of memory, so accessing outside the array has no memory assigned to it.
Local variables may be defined in another memory segment, e.g. stack., that is not the same as memory for dynamic allocation.
Here's an example.
An embedded system has On-Chip Memory and memory outside the "System on the Chip" (SOC). The On-Chip memory is faster, but there is less of it. The architects assign this memory to the stack. The memory outside the SOC is slower, but there is more of it, so it is assigned to the dynamic memory.
Another example:
The operating system supports virtual memory. Memory is paged onto the hard drive as needed. The operating system assigns a small amount of memory to your program. The small amount of memory will be assigned to the stack and the virtual memory will be assigned to dynamic memory.
Not all platforms are made of contiguous memory. Memory locations may also be assigned to hardware devices.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With