Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this strict aliasing violation? Can any type pointer alias a char pointer?

Tags:

c++

strict

I'm still struggling to understand what's allowed and not allowed with strict aliasing. With this concrete example is it violation of strict aliasing rule? If not, why? Is it because I placement new a different type into a char* buffer?

template <typename T>
struct Foo
{
    struct ControlBlock { unsigned long long numReferences; };
    Foo()
    {
        char* buffer = new char[sizeof(T) + sizeof(ControlBlock)];
        // Construct control block
        new (buffer) ControlBlock{};
        // Construct the T after the control block
        this->ptr = buffer + sizeof(ControlBlock);
        new (this->ptr) T{};
    }
    char* ptr;

    T* get() { 
        // Here I cast the char* to T*.
        // Is this OK because T* can alias char* or because
        // I placement newed a T at char*
        return (T*)ptr;
    }
};

For the record, a void* can alias any other type pointer, and any type pointer can alias a void*. A char* can alias any type pointer, but is the reverse true? Can any type alias a char* assuming the alignment is correct? So is the following allowed?

char* buffer = (char*)malloc(16);
float* pFloat = buffer;
*pFloat = 6; // Can any type pointer alias a char pointer?
// If the above is illegal, then how about:
new (pFloat) float; // Placement new construct a float at pointer
*pFloat = 7; // What about now?

Once I've assigned char* buffer pointer to the new allocation, in order to use it as a float buffer do I need to loop through and placement new a float at each place? If I had not assigned the allocation to a char* in the first place, but a float* to begin with, I'd be able to use it immediately as a float buffer, right?

like image 865
Zebrafish Avatar asked Aug 20 '21 13:08

Zebrafish


People also ask

What is the strict aliasing rule and why do we care?

The compiler and optimizer are allowed to assume we follow the aliasing rules strictly, hence the term strict aliasing rule. If we attempt to access a value using a type not allowed it is classified as undefined behavior(UB).

What is pointer aliasing?

If a function has two pointers pa and pb , with the same value, we say the pointers alias each other. This introduces constraints on the order of instruction execution. If two write accesses that alias occur in program order, they must happen in the same order on the processor and cannot be re-ordered.

Can alias be given to pointers?

In C++, references are just aliases for the thing that they refer to, the standard doesn't even require them to take up any storage. Trying to use a reference alias to make a reference to a pointer will not work because using the alias will only ever give you a pointer to a reference type.

Can alias be given to pointers in C?

Pointer aliasing is a hidden kind of data dependency that can occur in C, C++, or any other language that uses pointers for array addresses in arithmetic operations. Array data identified by pointers in C can overlap, because the C language puts very few restrictions on pointers.

What is the strict aliasing rule?

They introduced rules that state when pointer aliasing must not happen. Enter the strict aliasing rule. To facilitate compiler optimization, the strict aliasing rule demands that (in simple words) pointers to incompatible types never alias.

Can You alias a pointer to a type?

Pointers to compatible types (like the two ‘int’ pointers ‘x’ and ‘y’ in ‘silly’) are assumed to (potentially) alias. Let’s make the pointer types incompatible (‘short*’ vs. ‘int*’): As you can see, this time no load from memory is performed — 0 is returned instead.

Why do we use strict aliasing in GCC?

This is done because they referred to the same memory location. GCC compiler makes an assumption that pointers of different types will never point to the same memory location i.e., alias of each other. Strict aliasing rule helps the compiler to optimize the code.

What is aliasing in C programming?

Aliasing: Aliasing refers to the situation where the same memory location can be accessed using different names. For Example, if a function takes two pointers A and B which have the same value, then the name A [0] aliases the name B [0] i.e., we say the pointers A and B alias each other. Below is the program to illustrate aliasing in C:


Video Answer


2 Answers

Strict aliasing means that to dereference a T* ptr, there must be a T object at that address, alive obviously. Effectively this means you cannot naively bit-cast between two incompatible types and also that a compiler can assume that no two pointers of incompatible types point to the same location.

The exception is unsigned char , char and std::byte, meaning you can reinterpret cast any object pointer to a pointer of these 3 types and dereference it.

(T*)ptr; is valid because at ptr there exists a T object. That is all that is required, it does not matter how you got that pointer*, through how many casts it went. There are some more requirements when T has constant members but that has to do more with placement new and object resurrection - see this answer if you are interested.

*It does matter even in case of no const members, probably, not sure, relevant question . @eerorika 's answer is more correct to suggest std::launder or assigning from the placement new expression.

For the record, a void* can alias any other type pointer, and any type pointer can alias a void*.

That is not true, void is not one of the three allowed types. But I assume you are just misinterpreting the word "alias" - strict aliasing only applies when a pointer is dereferenced, you are of course free to have as many pointers pointing to wherever you want as long as you do not dereference them. Since void* cannot be dereferenced, it's a moo point.

Addresing your second example

char* buffer = (char*)malloc(16); //OK

// Assigning pointers is always defined the rules only say when
// it is safe to dereference such pointer.
// You are missing a cast here, pointer cannot be casted implicitly in C++, C produces a warning only.
float* pFloat = buffer; 
// -> float* pFloat =reinterpret_cast<float*>(buffer);

// NOT OK, there is no float at `buffer` - violates strict aliasing.
*pFloat = 6;
// Now there is a float
new (pFloat) float;
// Yes, now it is OK.
*pFloat = 7;
like image 79
Quimby Avatar answered Oct 25 '22 07:10

Quimby


Is this strict aliasing violation?

Yes.

Can any type pointer alias a char pointer?

No.

You can launder the pointer:

T* get() { 
    return std::launder(reinterpret_cast<T*>(ptr)); // OK
}

Or, you could store the result of the placement new:

Foo()
{
    ...
    this->ptr = new (buffer + sizeof(ControlBlock)) T{};
}
T* ptr;

T* get() { 
    return ptr; // OK
}

do I need to loop through and placement new a float at each place

Not since the proposal P0593R6 was accepted into the language (C++20). Prior to that, placement-new was required by the standard. You don't necessarily have to write that loop yourself since there are function templates for that in the standard library: std::uninitialized_fill_n, uninitialized_default_construct_n etc. Also, you can rest assured that a decent optimiser will compile such loop to zero instructions.

constexpr std::size_t N = 4;
float* pFloat = static_cast<float*>(malloc(N * sizeof(float)));

// OK since P0593R6, C++20
pFloat[0] = 6;

// OK prior to P0593R6, C++20 (to the extent it can be OK)
std::uninitialized_default_construct_n(pFloat, N);
pFloat[0] = 7;

// don't forget
free(pFloat);

P.S. Don't use std::malloc in C++, unless you need it for interacting with C API that requires it (which is a somewhat rare requirement even in C). I also recommend against reusal of new char[] buffer as it is unnecessary for the demonstrated purpose. Instead, use the operator ::new which allocates storage without creating objects (even trivial ones). Or even better, since you already have a template, let the user of the template provide an allocator of their own to make your template more generally useful.

like image 40
eerorika Avatar answered Oct 25 '22 05:10

eerorika