Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a std::array alias a fragment of a larger array?

Suppose we have a pointer T* ptr; and ptr, ptr+1, … ptr+(n-1) all refer to valid objects of type T.

Is it possible to access them as if they were an STL array? Or does the following code:

std::array<T,n>* ay = (std::array<T,n>*) ptr

invoke undefined behaviour?

like image 303
kinokijuf Avatar asked Apr 08 '16 20:04

kinokijuf


People also ask

Why array of reference is not possible?

An array of references is illegal because a reference is not an object. According to the C++ standard, an object is a region of storage, and it is not specified if a reference needs storage (Standard §11.3. 2/4). Thus, sizeof does not return the size of a reference, but the size of the referred object.

What is the relationship between an array name and a pointer?

An array is represented by a variable that is associated with the address of its first storage location. A pointer is also the address of a storage location with a defined type, so D permits the use of the array [ ] index notation with both pointer variables and array variables.

Is an array a pointer?

An array is a pointer. An array is considered to be the same thing as a pointer to the first item in the array. That rule has several consequences. An array of integers has type int*.


2 Answers

Yes, its an Undefined Behavior, a classic one...

First, understand that what you just did:

std::array<T,n>* ay = (std::array<T,n>*) ptr

can be translated as:

using Arr = std::array<T,n>;
std::array<T,n>* ay = reinterpret_cast<Arr*>( const_cast<TypeOfPtr>(ptr));

You've not just casted away all, const and volatile qualification but also casted the type. See this answer: https://stackoverflow.com/a/103868/1621391 ...indiscriminately casting away cv qualifications can also lead to UB.

Secondly, It is undefined behavior to access an object through a pointer that was casted from an unrelated type. See the strict aliasing rule (Thanks zenith). Therefore any read or write access through the pointer ay is undefined. If you are extremely lucky, the code should crash instantly. If it works, evil days are awaiting you....

Note that std::array is not and will never be the same as anything that isn't std::array.

Just to add... In the working draft of the C++ standard, it lists out explicit conversion rules. (you can read them) and has a clause stating that

.....

5.4.3: Any type conversion not mentioned below and not explicitly defined by the user ([class.conv]) is ill-formed.

.....


I suggest you cook up your own array_view (hopefully coming in C++17). Its really easy. Or, if you want some ownership, you can cook up a simple one like this:

template<typename T>
class OwnedArray{
    T* data_ = nullptr;
    std::size_t sz = 0;
    OwnedArray(T* ptr, std::size_t len) : data_(ptr), sz(len) {}
public:
    static OwnedArray own_from(T* ptr, std::size_t len)
    { return OwnedArray(ptr, len);  }

    OwnedArray(){}

    OwnedArray(OwnedArray&& o)
    { data_ = o.data_; sz = o.sz; o.data_=nullptr; o.sz=0; }

    OwnedArray& operator = (OwnedArray&& o)
    { delete[] data_; data_ = o.data_; sz = o.sz; o.data_=nullptr; o.sz=0; }

    OwnedArray(const OwnedArray& o) = delete;

    OwnedArray& operator = (const OwnedArray& o) = delete;    

    ~OwnedArray(){ delete[] data_; }

    std::size_t size() const { return sz; }

    T* data() return { data_; }

    T& operator[] (std::size_t idx) { return data_[idx]; }
};

...and you can roll out more member functions/const qualifications as you like. But this has caveats... The pointer must have been allocated the through new T[len]

Thus you can use it in your example like this:

auto ay = OwnedArray<decltype(*ptr)>::own_from(ptr, ptr_len);
like image 180
WhiZTiM Avatar answered Sep 19 '22 13:09

WhiZTiM


Yes, this invokes undefined behaviour. Generally you can't cast pointers to unrelated types between each other.

The code is no different from

std::string str;
std::array<double,10>* arr = (std::array<double,10>*)(&str);

Explanation: Standard does not provide any guarantee for any compatibility between std::array<T,n> and T*. It is simply not there. It doesn't say that std::array is trivial type either. Absent such guarantees, any conversion between T* and std::array<T,n> is undefined behavior on the same scale as conversion between pointers to any unrelated types.

I also fail to see what is the benefit of accessing already constructed dynamic array as an std::array.

P.S. Usual disclaimer. Cast, on it's own, is always 100% fine. It is indirection of resulted pointer which triggers the fireworks - but this part is omited for simplicty.

like image 34
SergeyA Avatar answered Sep 18 '22 13:09

SergeyA