Compare void pointers in constexpr [duplicate] - c++

C++20 allows heap allocation in constexpr functions as long as the memory does not leak. However GCC and Clang disagree on whether comparing the addresses of two dynamically allocated objects is a constant expression or not.
The following snippet can be compiled with Clang but not gcc.
constexpr bool foo() {
int* a = new int(4);
int* b = new int(4);
bool result = a == b;
delete a;
delete b;
return result;
}
constexpr bool x = foo(); // GCC: error: '(((int*)(& heap deleted)) == ((int*)(& heap deleted)))' is not a constant expression
The following works fine on both compilers
constexpr bool foo2() {
int a = 4;
int b = 5;
bool result = &a == &b;
return result;
}
constexpr bool x = foo2();
I'd assume that in order to delete the dynamic objects correctly the compiler must know whether the pointers point to the same objects or not, so I'd assume this is a GCC bug (or not yet fully implemented). Can anyone confirm this assumption? Or am I wrong?
Live example here.
Edit: Strangely, when I open the live example through the link provided, it suddenly compiles on gcc, too. But if I copy-paste it to a new compiler explorer instance, it fails again. Or if I reload it multiple times it fails every second time and compiles every other second time...

This is a gcc bug (#85428).
There's nothing in [expr.const]/5 that would cause evaluation of a == b to fail to be a constant expression. The only one there in which there is any question would be the one about undefined behavior. So we could go look at [expr.eq] to see what that says about pointer comparison:
If at least one of the operands is a pointer, pointer conversions, function pointer conversions, and qualification conversions are performed on both operands to bring them to their composite pointer type.
Comparing pointers is defined as follows:
If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object, the result of the comparison is unspecified.
Otherwise, if the pointers are both null, both point to the same function, or both represent the same address, they compare equal.
Otherwise, the pointers compare unequal.
Both pointers represent the address of different complete objects, with neither being null, so so we fall into the third bullet point and the pointers just compare unequal. No undefined or unspecified behavior here.
a == b should just yield false.

Related

null pointer dereference when used as an lvalue

Background
I have a class containing different members (custom run time constructed structs). And I have a compile time tuple containing pairs of pointer-to-member elements and strings. Compile time I need to check if every pointer-to-member and name is used only once in the list, and the custom structs check if they have an entry in the tuple (they know their own pointer-to-member). Having a tuple for this purpose increases the compile time dramatically, it would be great to identify the members in compile time with a void* array and not with a heterogeneous data struct.
Attempt to solve problem
As I read in this thread, dereferencing a nullptr is not always undefined behavior.
I read CWG-issue #315 also, that states:
We agreed the example should be allowed. p->f() is rewritten as (*p).f() according to 5.2.5 [expr.ref]. *p is not an error when p is null unless the lvalue is converted to an rvalue (4.1 [conv.lval]), which it isn't here.
I wanted to leverage this to get a normal pointer from a pointer-to-member (I don't want to dereference them, I just want to compare pointers-to-members from the same class but with different types).
So I created the following code:
#include <iostream>
class Test
{
int a;
public:
static constexpr inline int Test::*memPtr = &Test::a;
static constexpr inline int* intPtr = &(static_cast<Test*>(nullptr)->*Test::memPtr);
};
int main () {
std::cout << Test::intPtr << std::endl;
}
In my opinion the &(static_cast<Test*>(nullptr)->*Test::memPtr); expression uses the same approach as the code that was discussed in CWG-issue #315.
The code above compiles with MSVC but not with clang or gcc.
I checked if similar code that was mentioned in #315 compiles or not:
struct Test {
static constexpr int testFun () { return 10; }
};
int main ()
{
static constexpr int res{static_cast<Test*>(nullptr)->testFun()};
static_assert(res == 10, "error");
}
And yes, it does. test code
Should the construct I used in the first example be available in constexpr expressions (as undefined behavior is not allowed there)?
Fun fact: If I modify my original code and add a virtual destructor to the class then both MSVC and clang are happy with it, and gcc crashes. I mean literally, it segfaults.
Fun fact 2: If I remove the virtual destructor and make the class templated gcc and MSVC compiles it, but now clang complains.
From the standard on ->*'s behavior:
The expression E1->*E2 is converted into the equivalent form (*(E1)).*E2.
And for .*:
Abbreviating pm-expression.*cast-expression as E1.*E2, E1 is called the object expression. If the dynamic type of E1 does not contain the member to which E2 refers, the behavior is undefined.
The dynamic type of E1 (which dereferences a nullptr) does not exist, because it's a reference to no object. Therefore, the behavior of this expression is undefined.

Is comparing addresses of dynamically allocated objects allowed in C++20 constant expression?

C++20 allows heap allocation in constexpr functions as long as the memory does not leak. However GCC and Clang disagree on whether comparing the addresses of two dynamically allocated objects is a constant expression or not.
The following snippet can be compiled with Clang but not gcc.
constexpr bool foo() {
int* a = new int(4);
int* b = new int(4);
bool result = a == b;
delete a;
delete b;
return result;
}
constexpr bool x = foo(); // GCC: error: '(((int*)(& heap deleted)) == ((int*)(& heap deleted)))' is not a constant expression
The following works fine on both compilers
constexpr bool foo2() {
int a = 4;
int b = 5;
bool result = &a == &b;
return result;
}
constexpr bool x = foo2();
I'd assume that in order to delete the dynamic objects correctly the compiler must know whether the pointers point to the same objects or not, so I'd assume this is a GCC bug (or not yet fully implemented). Can anyone confirm this assumption? Or am I wrong?
Live example here.
Edit: Strangely, when I open the live example through the link provided, it suddenly compiles on gcc, too. But if I copy-paste it to a new compiler explorer instance, it fails again. Or if I reload it multiple times it fails every second time and compiles every other second time...
This is a gcc bug (#85428).
There's nothing in [expr.const]/5 that would cause evaluation of a == b to fail to be a constant expression. The only one there in which there is any question would be the one about undefined behavior. So we could go look at [expr.eq] to see what that says about pointer comparison:
If at least one of the operands is a pointer, pointer conversions, function pointer conversions, and qualification conversions are performed on both operands to bring them to their composite pointer type.
Comparing pointers is defined as follows:
If one pointer represents the address of a complete object, and another pointer represents the address one past the last element of a different complete object, the result of the comparison is unspecified.
Otherwise, if the pointers are both null, both point to the same function, or both represent the same address, they compare equal.
Otherwise, the pointers compare unequal.
Both pointers represent the address of different complete objects, with neither being null, so so we fall into the third bullet point and the pointers just compare unequal. No undefined or unspecified behavior here.
a == b should just yield false.

Constexpr version of function gives different result when called the same way

main.cpp:
#include <iostream>
static constexpr bool f1()
{
auto p = new int(1);
delete p;
auto q = new int(2);
delete q;
return p == q;
}
static bool f2() // Same body as f1
{
auto p = new int(1);
delete p;
auto q = new int(2);
delete q;
return p == q;
}
int main()
{
constexpr bool i1 = f1();
std::cout << i1 << std::endl;
auto i2 = f2();
std::cout << i2 << std::endl;
}
Compilation command line:
clang++ -std=c++20 -pedantic-errors main.cpp -o prog
Output from running prog (this is what I got, but may be different for you):
0
1
How is this possible? How is it even possible that I am allowed to define f1 that way given that it has unspecified behaviour?
Deleting a pointer invalidates it.
Any use of an invalid pointer value has implementation-defined behavior (except for indirecting through and passing to a deallocation function, which have undefined behaviour; neither is done in the example).
In the example that behaviour happened to be different in two slightly different cases.
How is this possible?
The compiler produced a program that outputs "0\n1". It is possible.
If you want to know if this conforms to the standard: Yes.
Whether this is intentional by the implementation... I suspect not directly, but rather by coincidence. My entirely hypothetical guess about the implementation:
There may be a piece of logic that sets invalid pointers to null. This has the useful side-effect that programs that have "use after free" bug (undefined behaviour) are less likely to read/write arbitrary memory (heap smashing) and instead avoid that due to null pointer check, or outright crash due to indirecting through null pointer. This potentially would reduce the severity of security vulnerabilities caused by such bug. As a side-effect, two unspecified values that would happen to be null pointers would also happen to compare equal.
But in constexpr case there may be another piece of logic which analyses that the pointers never point to same object and therefore are never equal, and constant-fold the return value as false before the null "protection" occurs.
Standard quote:
[basic.stc]
When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values. Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior.31
Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault.
⮥

Is it legal to cast a pointer to array reference using static_cast in C++?

I have a pointer T * pValues that I would like to view as a T (&values)[N]
In this SO answer https://stackoverflow.com/a/2634994/239916, the proposed way of doing this is
T (&values)[N] = *static_cast<T(*)[N]>(static_cast<void*>(pValues));
The concern I have about this is. In his example, pValues is initialized in the following way
T theValues[N];
T * pValues = theValues;
My question is whether the cast construct is legal if pValues comes from any of the following constructs:
1:
T theValues[N + M]; // M > 0
T * pValues = theValues;
2:
T * pValues = new T[N + M]; // M >= 0
Short answer: You are right. The cast is safe only if pValues is of type T[N] and both of the cases you mention (different size, dynamically allocated array) will most likely lead to undefined behavior.
The nice thing about static_cast is that some additional checks are made in compile time so if it seems that you are doing something wrong, compiler will complain about it (compared to ugly C-style cast that allows you to do almost anything), e.g.:
struct A { int i; };
struct C { double d; };
int main() {
A a;
// C* c = (C*) &a; // possible to compile, but leads to undefined behavior
C* c = static_cast<C*>(&a);
}
will give you: invalid static_cast from type ‘A*’ to type ‘C*’
In this case you cast to void*, which from the view of checks that can be made in compile time is legal for almost anything, and vice versa: void* can be cast back to almost anything as well, which makes the usage of static_cast completely useless at first place since these checks become useless.
For the previous example:
C* c = static_cast<C*>(static_cast<void*>(&a));
is no better than:
C* c = (C*) &a;
and will most likely lead to incorrect usage of this pointer and undefined behavior with it.
In other words:
A arr[N];
A (&ref)[N] = *static_cast<A(*)[N]>(&arr);
is safe and just fine. But once you start abusing static_cast<void*> there are no guarantees at all about what will actually happen because even stuff like:
C *pC = new C;
A (&ref2)[N] = *static_cast<A(*)[N]>(static_cast<void*>(&pC));
becomes possible.
Since C++17 at least the shown expression isn't safe, even if pValues is a pointer to the first element of the array and the array is of exactly matching type (including excat size), whether obtained from a variable declaration or a call to new. (If theses criteria are not satisfied it is UB regardless of the following.)
Arrays and their first element are not pointer-interconvertible and therefore reinterpret_cast (which is equivalent to two static_casts through void*) cannot cast the pointer value of one to a pointer value of the other.
Consequently static_cast<T(*)[N]>(static_cast<void*>(pValues)) will still point at the first element of the array, not the array object itself.
Derferencing this pointer is then undefined behavior, because of the type/value mismatch.
This can be potentially remedied with std::launder, which may change the pointer value where reinterpret_cast can't. Specifically the following may be well-defined:
T (&values)[N] = *std::launder(static_cast<T(*)[N]>(static_cast<void*>(pValues)));
or equivalently
T (&values)[N] = *std::launder(reinterpret_cast<T(*)[N]>(pValues));
but only if the pointer that would be returned by std::launder cannot be used to access any bytes that weren't accessible through the original pValues pointer. This is satified if the array is a complete object, but e.g. not satisfied if the array is a subarray of a two-dimensional array.
For the exact reachability condition, see https://en.cppreference.com/w/cpp/utility/launder.

Why this code compiles?

I have a code.
class A
{
public:
int foo(int i)
{
return i;
}
};
int foo(int i)
{
return i;
}
int (A::*ptrFoo)(int) = NULL;
int (*_foo)(int) = NULL;
int main()
{
ptrFoo = &A::foo;
_foo = foo;
(*_foo)++++++++++++++(10); //This dont compile...
A a;
(a.*ptrFoo)+++++++++++++++++(10); //This compiles ????
}
please tell me what it is ?? a undefined behavior or what ??? I compiled it in VS2008.Strangely the last line of code compiles successfully.
Neither expression should compile: in C++, you cannot perform arithmetic on a pointer to a function or member function, or on a function type or member function. The two expressions in your program attempt to perform arithmetic on a function and on a member function, respectively.
If your compiler accepts the second expression, it is due to a bug in the compiler.
First note that pointer to functions are different with pointer to member functions.
Your first example is a pointer to an ordinary function. It contains the real memory address of the function. When you dereference it ((*_foo)) you get the function itself, and arithmetic operations including ++ on a function (function pointer) are meaningless.
The second one is another story, pointers to member functions of classes do not carry the address of the function in memory. Actually how compiler manages member functions is implementation-specific. A pointer to a member function may contain some address or maybe some compiler-specific information. Arithmetic on this type is also meaningless.
Therefore we don't know what the value of (a.*ptrFoo) ever is, but in your case MSVC2008 managed to compiler it, either because of a bug or by design.
By the way, GCC does not compile any of the two statements and threw errors on both.
The above is true whether you put even number of +'s or odd numbers; we are doing arithmetic anyway. (If there are an odd number of +'s then there is no function call, as in your second example you are incrementing the function 8 times then the last remaining + adds 10 to the result. Again, this doesn't matter: we are trying to change a function/member function pointer.)