c++operating-system

Non-trivial designated initializers


I am currently working on an OS and working on the utils section. Specifically, on the tmp dir. I have came up with this code,


#include <stdint.h>
#define HIGH_KERNEL   0xc0000000
#define PAGE_SIZE     4096
#define PAGE_PRESENT  (1 << 0)
#define PAGE_WRITE    (1 << 1)
#define PAGE_HUGE     (1 << 7)

__attribute__((__aligned__(PAGE_SIZE)))
uint32_t tmp_pgdir[1024] = {
  [0] = 0x0 | PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE,
  [HIGH_KERNEL >> 22] = 0x0 | PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE,
};

Now, afaik non-trivial designated initializers are not supported in C++, so one option I thought of is compiling the file as a C file. Now, I wonder, is there a workaround? Is it actually the ideal solution?

Thanks in advance.


Solution

  • The [/*...*/] = initializer syntax for arrays isn't standard C++ at all.

    But you don't need it in C++. You can just initialize the array in the same way that you would do it at block scope with the help of an immediately-invoked lambda and std::array:

    alignas(PAGE_SIZE) constinit auto tmp_pgdir = []{
      std::array<std::uint32_t, 1024> ret{}; // {} initializes to zero
      ret[0] = 0x0 | PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE;
      ret[HIGH_KERNEL >> 22] = 0x0 | PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE;
      return ret;
    }();
    

    alignas(PAGE_SIZE) is the standard-C++ form for the alignment attribute.

    constinit assures that the initialization happens at compile-time, not runtime. It makes the compiler complain if initialization at compile-time is not possible.

    std::array has the benefit of having the normal function passing behavior of non-array object types, so it can be returned from the function directly.

    The lambda is not strictly necessary. If you prefer this can be a constexpr named function to be called in the initializer as well.

    The above requires C++20 for constinit, otherwise only C++14. But without constinit you are yourself responsible for making sure that the initialization can be done at compile-time, i.e. is a constant expression. In particular, if you don't use a lambda, you are then responsible for assuring that the function that you call is actually marked constexpr.

    All of the #define constants can be constexpr auto variables in C++ instead as well. There is no good reason to #define such constants in C++11 and later (except if you need them in the preprocessor or a header shared with C sources).

    Also, if you don't intent to modify tmp_pgdir, then declare it constexpr instead of constinit. This, in addition to assuring compile-time initialization, will make the variable non-modifiable as well and gets rid of the C++20 requirement for constinit.

    In order to obtain a pointer to the beginning of the array use tmp_pgdir.data(), &tmp_pgdir[0] or std::to_address(std::begin(tmp_pgdir)) instead of just std::begin(tmp_pgdir), &tmp_pgdir or tmp_pgdir. &tmp_pgdir will probably also work if the target isn't aware of the pointer type, but it is formally wrong. std::begin(tmp_pgdir) directly may or may not work depending on implementation details.

    To get a pointer to one-past-the end use std::to_address(std::end(tmp_pgdir)) or tmp_pgdir.data() + std::size(tmp_pgdir). Again, just &tmp_pgdir + 1 or std::end(tmp_pgdir) or &tmp_pgdir[std::size(tmp_pgdir)] will probably work as well, but are formally incorrect or implementation-dependent.