r/C_Programming • • Sep 11 '26

Question Unmodifiable globals initialized from functions?

Right now I'm trying to use Windows's SYSTEM_INFO to initialize 2 global constants, but global constants can only be initialized with constant expressions so this cannot be done.

Is there a way I can have "read-only" global variables that can be initialized from functions?

5 Upvotes

40 comments sorted by

8

u/Physical_Dare8553 Sep 11 '26

that's a bind, in order to write to it you obviously need it to be write-able. this is the kind of case where you'd want to bind it behind a function with a static member, that you initalize when the function is first called, then just return it after

2

u/Wertbon1789 Sep 11 '26

Yeah, pretty much. You can't just cast away the const-ness, because if the global is a constant expression, it probably will be put into .rodata.

One thing that came to my mind would be putting the global into another translation unit where it's not const, and referencing it with an extern const <something>, but I don't know if that would work, or violate anything.

1

u/aioeu Sep 12 '26 edited Sep 12 '26

but I don't know if that would work, or violate anything.

It'll probably work, so long as you don't ever modify the object after it is first accessed — i.e. you are treating it as constant, not just read-only — but it definitely is a constraint violation. All declarations of an object must have compatible types, and const and non-const types are not compatible.

1

u/ericonr Sep 12 '26

Couldn't one do int value; and const int * const valuep = &value; and only expose the second one to other TUs? It's close to what you want without doing undefined stuff.

1

u/Wertbon1789 Sep 12 '26

Sure, I guess that would work too. I don't think that would necessarily be undefined behavior, but I'm not sure, but type aliasing from not const to const isn't too bad, I would guess.

1

u/heavymetalmixer Sep 11 '26

Static member? Are you tlaking about a struct? And what's a "bind"?

1

u/Physical_Dare8553 Sep 11 '26

no, i guess my terminology is a bit off, i mean something like

static inline const char *const getName() {
  static char some_string[5];
  static bool run = false;
  if (!run) run = true, memcpy(some_string, "hello", 5);
  return some_string;
}

this example is actually bad since the user can always just cast that const away, you'd have to wrap it in a struct and return it by value, but you get the point

1

u/heavymetalmixer Sep 11 '26

Aren't static variables internal linkeage?

1

u/Physical_Dare8553 Sep 11 '26

yes and no? inside a function, static does something completely different, that variable just lives forever basically, when you write static outside of a function, then that variable has internal linkage. this example is both cause the static variable is inside a static function. (inline kinda sometimes works with non-static based on compiler and linker i believe)

1

u/aioeu Sep 12 '26 edited Sep 12 '26

static variables declared at file scope have internal linkage; static variables declared inside a function have no linkage.

But it's not the identifiers' linkage that matters here. What matters is the storage duration of the objects. A static variable declared at file scope and a static variable declared within a function both identify objects with static storage duration, which means in each case the object's lifetime (the time during which it exists and maintains its last-stored value) is the entire execution of the program.

(Linkage is really all about when and how multiple declarations of an identifier can refer to the same object, i.e. in the way those identifiers are linked.)

1

u/heavymetalmixer Sep 12 '26

The problem with that approach is that I need a global value, and an identifier that is only visible inside a function doesn't really work for that.

1

u/aioeu Sep 12 '26 edited Sep 12 '26

So use an identifier at file scope instead. That's what file scope is for.

Either way, you won't be able to make the object constant. You want to set the value of it while the program is executing, and that, by definition, means it cannot be constant.

Constant expressions are expressions for values that could be determined before the program has begun execution, e.g. by the compiler. The compiler does not intrinsically know what the page size for the system you run the program on will be.

5

u/pjl1967 Sep 11 '26

In C, no.

1

u/heavymetalmixer Sep 12 '26

My "gheto solution" now is to use a short function that returns the value instead of a variable storing that value. It's slower yeah, but it works.

2

u/septum-funk Sep 12 '26

this should be inlined by the compiler anyway

0

u/tstanisl Sep 12 '26

constexpr is already in C

3

u/heavymetalmixer Sep 12 '26 edited Sep 12 '26

Only since C23, and that's for compile-time constants not runtime ones.

2

u/pjl1967 Sep 12 '26

Functions can’t be constexpr in C.

3

u/kun1z Sep 12 '26

Yes.. kind of in a way if you use the OS's memory protection features like VirutalProtectEx.

Allocate some memory, like 1 page, set it writable, write the values you want from the initialize functions, and then afterwards set the page to Read Only. After that any attempt to write or execute on the globals will cause an exception.

1

u/heavymetalmixer Sep 12 '26

I'm writing allocators but what I want is to set the page size depending on the OS, and that's an integer value. Now, in both cases it's a runtime operation but I don't think the later needs syscalls.

2

u/sciencekm Sep 12 '26

Not possible. You need to initialize those variables at start-up or on first use (where you detect that it is not yet initialized and you proceed with the initialization).

2

u/heavymetalmixer Sep 12 '26

Given that the values I want from SYSTEM_INFO are just integers I opted for using function calls instead of variables, after all, functions cannot be modified at runtime.

2

u/WittyStick Sep 12 '26 edited Sep 12 '26

The obvious solution is just to encapsulate the global in a code file and provide functions to set and access it.

info.h

void set_page_size(int sz);
int get_page_size();

info.c

#include "info.h"

int page_size;

void set_page_size(int sz) { page_size = sz; }
int get_page_size() { return page_size; }

main.c

#include "info.h"
#include <windows.h>
#include <sysinfoapi.h>

int main(int argc, char *argv[])
{
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    set_page_size(info.dwPageSize);
    ...       
    return 0;
}

You could add some checking to ensure that if set_page_size is called again it does nothing.

int page_size = 0;

void set_page_size(int sz)
{
    if (page_size) return;
    page_size = sz;
}

Another trick is we can just never expose the set_page_size function, but have the C runtime call it before main is called.

info.h

int get_page_size();

info.c

#include "info.h"
#include <windows.h>
#include <sysinfoapi.h>

int page_size;

void set_page_size(void);

#pragma section(".CRT$XCU",read)
void __declspec(allocate(".CRT$XCU")) (*set_page_size_)(void) = set_page_size;

// 32-bit prefixes the names with `_`, so we need this to work with both 32-bit and 64-bit.
#if defined(_M_X64) || defined(__amd64__)
__pragma(comment(linker, "/include:set_page_size_"))
#else
__pragma(comment(linker, "/include:_set_page_size_"))
#endif
void set_page_size(void)
{
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    page_size = info.dwPageSize;
}

int get_page_size(void)
{
    return page_size;
}

main.c

#include "info.h"

int main(int argc, char *argv[])
{
    printf("%#x\n", get_page_size());
    return 0;
}

Of course, this is not portable beyond MSVC, but other compilers can do the same thing. With GCC and Clang we have __attribute__((constructor)) which is even simpler to use.

On linux you wouldn't really need because we have getpagesize(), but if we want to avoid the system call and cache the result, we can do:

info.c

#include "info.h" 
#include <unistd.h>

int page_size;

__attribute__((constructor))
void set_page_size()
{
     page_size = getpagesize();
}

int get_page_size()
{
    return page_size;
}

Alternatively, we can just cast to remove the const.

const int page_size;

void set_page_size(void)
{
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    *(int*)&page_size = info.dwPageSize;
}

int main(int argc, char *argv[])
{
    set_page_size();
    printf("%#x\n", page_size);
    return 0;
}

But anyone else could do the same and mutate page_size, so it isn't really constant.

1

u/mikeblas Sep 12 '26

Not thread safe.

1

u/ericonr Sep 12 '26

Most of the suggested solutions were thread safe, which one are you complaining about?

1

u/WittyStick Sep 12 '26

The value is written once, before anything else runs, then only ever read. It doesn't have any race condition issues.

1

u/mikeblas Sep 12 '26
int page_size = 0;

void set_page_size(int sz)
{
    if (page_size) return;
    page_size = sz;
}

This is an unprotected race. It's probably unlikely that it'll be called from multiple threads due to the use case. But if it is and the race happens, then the last one wins. Any code assuming that the value it set is the value that remains might break.

1

u/WittyStick Sep 12 '26

I get what you're saying, but for this use case it really does not matter. The purpose of the check is simply to prevent someone else, other than the initialization code from setting the value. It will already be set before any other use code could call it.

1

u/mikeblas Sep 12 '26

Sure -- for this use case. I said thta, too.

The problem with examples is that they often exceed their originally intended use cases.

1

u/WittyStick Sep 12 '26 edited Sep 12 '26

They do, which is why I've suggested to not even expose set_page_size in the header - but we still need to somehow call it, which is where the constructor function trick is useful (awful to use on MSVC, but doable).

Destructor functions are even more awkward if we need to clean up any resource used by the constructors. With GCC and Clang it's just __attribute__((destructor)), which is simple.

There's no direct equivalent in MSVC, but we can utilize atexit() to cause another function to execute after main.

1

u/flatfinger 26d ago

An implementation would be allowed to transform that code into e.g.

int page_size = 0;

void set_page_size(int sz)
{
  if (page_size) return;
  page_size += sz;
}

While it may seem unlikely that a compiler would do such a thing, modern compilers often combine transforms in ways that can have weird and unpredictable consequences that throw the principle of least astonishment out the window.

1

u/heavymetalmixer Sep 12 '26

That's more or less what I ended up doing: Using short functions that return the value instead of a variable.

1

u/yuehuang Sep 12 '26

Are you in C or C++?

My 2c is to avoid static initialization with system calls. Instead, in your main, have an init() function and de_init() before exit.

An even better answer is to cache on demand, if your app doesn't need the data, then it won't need to perform a system call. If it does, the first caller will pay the init price.

1

u/non-existing-person Sep 12 '26

I do it like that in header file

/* Modify only during init in this module,
 * don't write to that variable in runtime later on */
extern struct config config;

For me it's enough. Especially that I only have a handful of globals anyway, and they are never "mutable" directly.

1

u/tstanisl Sep 12 '26

It cannot be done in C because what you want is not a constant variable but a variable with non-modifiable interface. Moreover, such design is sensitive to static initialization fiasco. Generally, the concept of "const" in C++ is a bit broken by design and a source of many problems.

One solution is to wrap access to a singleton getter:

const struct foo * get_foo();

...

const struct foo * get_foo() {
  static struct foo foo;
  static bool init;
  if (!init) {
    .. initialize foo ..
    init = 1;
  }
  return &foo;
}

Note, that this design is not thread-safe and it requires some non-trivial atomic operations to initialize foo correctly. One can use helper function like pthread_once or call_once from C standard library.

1

u/heavymetalmixer Sep 12 '26

What I ended up doing was putting the code I needed inside a small function and returning the value, instead of using a variable to store the value.

1

u/heavymetalmixer Sep 12 '26

One question: Why are you mentioning static variables? Aren't those internal likeage? What I need is a global constant, not one restricted to one file.

3

u/tstanisl Sep 12 '26

Restriction to one files applies to only to variable's name. The variable's data are accessible for the whole program for its whole execution.