Is it good practice to access a pointer variable by dereferencing a pointer to a pointer, which points to a different type or void
? Could this break strict aliasing rules? C and C++ have some differences in aliasing rules. In this question we focus on C++. The other question considering C can be found here. In the following example a double*
is accessed as a void*
.
int create_buffer(void** ptr, ...)
{
*ptr = malloc(...);
...
}
int main(void)
{
double* buffer;
// The problematic code is here, double**
// is coerced to void**, which is later
// dereferenced by the function
create_buffer(reinterpret_cast<void**>(&buffer), ...);
...
}
If this is causes UB, what about the following?
// process A
int* p; ...
printf("%p", p); // UB?
// process B
int* p;
scanf("%p", &p); // UB?
This looks like a bad example, but what if two processes talk to each other through pipes, and eventually one process passes a pointer to globally allocated memory to the other process.
Is it good practice to access...
No. void*
is not the go to type for polymorphism and reuse in C++, even without considering the aliasing issues in your original code. With a rich template mechanism available you can make your code strongly typed and safer to boot. The obvious improvement is to use templates, and type safe allocation:
template<typename T>
int create_buffer(T** ptr, ...)
{
*ptr = new T[...];
...
}
But to go off on a tangent, this is still not how good C++ will look like. Good C++ is about managing complexity correctly. And tracking a buffer is a complex task. The good C++ approach is to not do it by hand, but to encapsulate it in a dedicated class (template). In fact, this is such a common task, that the standard library provides a solution.
The good practice is to use std::vector<double>
instead of the buffer creation function. A class template for a type generic task will often beat any use of void*
. That will avoid any aliasing issues entirely, since the correct type is always used.
This is UB as you are assigning a double*
variable as if it was a void*
variable (It has the same effect as reinterpret_cast<void*&>(buffer) = malloc(...);
in C++. This would be OK on most systems as void*
and double*
are usually exactly the same, but it is still UB, so might not work on all implementations).
A solution would be to assign to a different variable and then reassign it:
int main(void)
{
double* buffer;
{
void* result;
create_buffer(&result, ...);
buffer = (double*) result; // Cast needed for C++
}
...
}