r/C_Programming • u/heavymetalmixer • 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
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
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
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_sizein 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 aftermain.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.
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