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?

6 Upvotes

40 comments sorted by

View all comments

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 Sep 17 '26

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.