1

I have some classes designed to hold a pointer to some variables, and some other properties which are related to variables. Actually, I'm trying to design a CANopen stack for STM32 and I'm planning to use these classes to represent the object dictionary.

Here are the simplified versions of the classes:

class Object {
public:
    constexpr Object(int index) : index{index} {}
private:
    int index;
};

class Single : public Object {
public:
    constexpr Single(int index, void* var, std::size_t size)
    : Object{index}, ptr{var}, size{size} {}
private:
    void* ptr;
    std::size_t size;
};

class Array : public Object {
public:
    constexpr Array(int index, void* var, std::size_t size, std::size_t length)
    : Object{index}, ptr{var}, size{size}, length{length} {}
private:
    void* ptr;
    std::size_t size;
    std::size_t length;
};

And I wish to create an array of these objects. The code below compiles without problems:

int a {1}; // Single variable
float b[] {2.0, 3.0}; // Array

const Object* dictionary[] {
    new Single(0x1000, &a, sizeof(int)),
    new Array(0x1001, b, sizeof(float), std::size(b))
};

The problem is, I try to avoid using heap (dynamic allocation) as much as possible. Usage of dynamic allocation in embedded systems is a debated subject, but my main concern is memory utilization: STM32 microcontrollers have a lot more flash ROM than they have RAM, so it's preferable to mark variables as const, so the linker can place them into ROM instead of RAM.

But when a modify the code as below, it doesn't compile:

const Object* dictionary[] {
    &Single(0x1000, &a, sizeof(int)),
    &Array(0x1001, b, sizeof(float), std::size(b))
};

It gives error: taking address of rvalue error.

I can think of one solution:

const Single a_obj {0x1000, &a, sizeof(int)};
const Array b_obj {0x1001, b, sizeof(float), std::size(b)};

const Object* dictionary[] {
    &a_obj,
    &b_obj
};

This works without problems, and places a_obj & b_obj into ROM. But I think it looks ugly. It forces me to define and maintain additional set of variables, and imagine what if you have maybe ~20 of them. Is there a cleaner way?

Tagli
  • 2,412
  • 2
  • 11
  • 14
  • You can't have your cake and eat it too. If you want to have an array of pointers, where all those pointers are initialised to point at a statically allocated objects, then the objects need to be defined (in particular, initialised) before the array. Hence the approach that you think looks ugly. Be thankful that the compiler rejects your attempt to save addresses of temporaries - since (if compilation was allowed) the objects would cease to exist immediately after the array was initialised, and the array would then be a collection of dangling pointers. – Peter Jan 18 '21 at 10:41
  • 1
    Incidentally, it is not just "use of heap" that is debated for embedded systems. It is actually often patterns of arbitrary allocation, deallocation, and reallocation that are discouraged. The challenge, with uncontrolled dynamic memory allocation (allocating and deallocating whatever you like, when you like) is that there can be no upper bound on memory consumption and programmers feel that controlling their memory allocation (not exceeding a defined upper bound of memory usage) is too "restrictive". – Peter Jan 18 '21 at 10:47
  • Actually, I use some `new`s in this project for one-time object initialization but I never use `delete`. The main problem for me is the waste of valuable RAM for constant objects. – Tagli Jan 18 '21 at 10:56
  • 1
    What if you use stack allocator? Like this one https://howardhinnant.github.io/stack_alloc.html – kreuzerkrieg Jan 18 '21 at 11:03
  • 1
    @kreuzerkrieg Thanks for the link, it looks interesting and surely useful for embedded projects. However, it cannot place data on flash ROM of STM32. For this, the data must be created during compile time. – Tagli Jan 18 '21 at 11:29
  • @peter I get your position. But mine is, well, let them eat cake. – Yakk - Adam Nevraumont Jan 19 '21 at 01:07

2 Answers2

1

I'm a bit insane, so...

template<int index, auto& var>
constexpr Single Single_v{index, std::addressof(var), sizeof(var));
template<class T, std::size_t N>
constexpr Array MakeArray( int index, T(&arr)[N] ) {
  return {index, arr, sizeof(T), N};
}
template<std::size_t index, auto& Arr>
constexpr Array Array_v = MakeArray( index, Arr );

const Object* dictionary[] {
  &Single_v<0x1000, a>,
  &Array_v<0x1001, b>,
};

that might work.

Note that two objects with the same parameters end up being the same object.

Those template variables can have their address taken, and are constexpr.

We could even go further

template<class T, std::size_t N>
constexpr Array MakeObject(int index, T(&arr)[N]) {
    return {index, arr, sizeof(T), N};
}

template<class T>
constexpr Single MakeObject(int index, T& obj) {
    return {index, &obj, sizeof(T)};
}


template<int index, auto& x>
constexpr auto Object_v = MakeObject(index, x);

now you can just

const Object* dictionary[] {
  &Object_v<0x1000, a>,
  &Object_v<0x1001, b>,
};

and we get the compile-time polymorphically correct object type.

There are limitations to what you can do with template parameter arguments, but I'm not seeing any being hit here.

Live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

Unless your planning on heap allocation (which as you said would be debatable on microcontrollers), you'll most likely have to do it as you purposed (instantiate it globally and add the address).

Why?
When your talking about pointers, what you need is to point to a valid block of memory. When you do something like &Array(0x1001, b, sizeof(float), std::size(b)), your basically trying to access an address of raw data (hence the error stating rvalue).

A simple example would be this,

int* pInt = &10;    // This is essentially what you have done.

Note: rvalue does not strictly translate to raw data.

Thats the reason for the error: taking address of rvalue.

This works without problems, and places a_obj & b_obj into ROM. But I think it looks ugly. It forces me to define and maintain additional set of variables, and imagine what if you have maybe ~20 of them

This is anyway the case of heap allocated memory, you have to manage its life time. The only plus side of instantiating it statically like what you purpose is that you don't have to explicitly delete the allocated memory block.

D-RAJ
  • 3,263
  • 2
  • 6
  • 24