Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clang complains: "pointer is initialized by a temporary array"

I have an array of (pointers to) arrays of different lengths, which I learned I could define using compound literals:

const uint8_t *const minutes[] = {
    (const uint8_t[]) {END},
    (const uint8_t[]) {1, 2, 3, 4, 5 END},
    (const uint8_t[]) {8, 9, END},
    (const uint8_t[]) {10, 11, 12, END},
    ...
}; 

gcc accepts this just fine, but clang says: pointer is initialized by a temporary array, which will be destroyed at the end of the full-expression. What does this mean? The code seems to be working, but then again, lots of things seem to work when they point to memory that's no longer allocated. Is this something I need to worry about? (Ultimately I only really need it to work with gcc.)

Update: Something fishy is going on. It says here that:

Compound literals yield lvalues. This means that you can take the address of a compound literal, which is the address of the unnamed object declared by the compound literal. As long as the compound literal does not have a const-qualified type, you can use the pointer to modify it.

   `struct POINT *p;
   p = &(struct POINT) {1, 1};

This example code seems to be doing exactly what I'm trying to do: a pointer to something defined by a compound literal. So is the clang error message legitimate? Will this end up pointing to unallocated memory when compiled with either clang or gcc?

Update 2: Found some documentation: "In C, a compound literal designates an unnamed object with static or automatic storage duration. In C++, a compound literal designates a temporary object, which only lives until the end of its full-expression." So it seems that clang is right to warn about this, and gcc probably ought to as well, but doesn't, even with -Wall -Wextra.

I can't guess why a useful C feature was removed from C++, and no elegant alternative way of accomplishing the same thing was provided.

like image 831
Josh Avatar asked Jul 03 '15 17:07

Josh


3 Answers

Well, clang is right, and this shall be done that way:

namespace elements
{
    const uint8_t row1[] = {END};
    const uint8_t row2[] = {1, 2, 3, 4, 5, END};
    ...
}
const uint8_t *const minutes[] = {
    elements::row1,
    elements::row2,
    ...
}; 

You can think of more C++ solution, like using std::tuple:

#include <tuple>

constexpr auto minutes = std::make_tuple(
    std::make_tuple(), 
    std::make_tuple(1,2,3,4,5),
    std::make_tuple(8,9,10)); 

#include <iostream>
#include <type_traits>
int main() {
    std::cout << std::tuple_size<decltype(minutes)>::value << std::endl;
    std::cout << std::tuple_size<std::remove_reference_t<decltype(std::get<1>(minutes))>>::value << std::endl;
}
like image 129
PiotrNycz Avatar answered Nov 15 '22 01:11

PiotrNycz


Well that means, this expression

(const uint8_t[]) {1, 2, 3, 4, 5 END},

creates a temporary object — temporary because it doesn't have any name which can last beyond the expression of which it is a part — which gets destroyed at the end of the full expression, which means this:

 };

defines "the full expression", at which point all the temporary objects will get destroyed, and the array of pointers minutes holds pointers which point to destroyed objects, which is why the compiler is giving warning.

Hope that helps.

like image 23
Nawaz Avatar answered Nov 15 '22 01:11

Nawaz


update: thanks to deniss for pointing out the flaw in the original solution.

static constexpr uint8_t END = 0xff;

template<uint8_t...x>
const uint8_t* make()
{
    static const uint8_t _[] = { x..., END };
    return _;
}

const uint8_t* const array[] = {
    make<>(),
    make<1, 2, 3, 4, 5>(),
    make<8, 9>(),
    make<10, 11, 12>()
};
like image 1
Richard Hodges Avatar answered Nov 15 '22 02:11

Richard Hodges