distro.obs
distro.obs

Reputation: 181

Making an variable constant after constructing it

I have a function that returns an array of strings depending on the OS running the code.

int arrayLength = getMeThatArrayLength();
char* someArray[arrayLenth];

populateTheArray(&someArray, arrayLength);

Once generated, the array should be immutable moving forward. The data returned should not change throughout the program's lifetime.

Like if I had stored the data in these sort of variables:

const int cArrayLength;
const char* const* cSomeArray;

where cArrayLength would hold whatever arrayLength has and cSomeArray would hold whatever someArray has.

Direct assigning throws error and I understand why.

const int cArrayLength = arrayLength;
const char* const* cSomeArray = someArray;

Maybe I need an immutable pointer to the mutable array? I'm fairly new to C so I'm not completely sure but maybe the pointer could point to the array but it'd be immutable from the pointer's perspective and as an extensions from the function's which is holding that immutable pointer.

Is there a way of doing that?

Upvotes: 4

Views: 115

Answers (1)

Petr Skocik
Petr Skocik

Reputation: 60143

C doesn't let you access non-const extern/static/_Thread_local data via const-qualified declarations.

You can't have (officially; practically it tends to work) a global int arrayLength; that's exposed elsewhere (in another translation unit) as extern int const arrayLength;.

What you can have is a static/static _Thread_local writable global that's accessed via an accessor that returns a pointer-to-const pointer to it.

Example code:

//PUBLIC HEADER
struct myvec{
    int arrayLegth;
    char const*const* someArray;
};
struct myvec const* getmyvec(void); 
/*const on structs works as if it make each memeber const:
     int const arrayLength
     char const*const*const someArray;
*/

///PRIVATE IMPLEMENTATION (in a C file that includes the header)
static struct myvec internal;
struct myvec const* getmyvec(void)
{
    if(internal.someArray) return &internal;
    //(allocate?+) fill internal ...
    return &internal;
}

The accessor method (as well as the officially unsupported way of exposing int x; as extern int const x;) rely on the type system.

The underlying data won't be in write-protected memory like it usually (implementation detail) would be if it was static/extern/_Thread_local data that was originally declared const. This means that you can cast away the const and then write to the data via the new pointer.

Using system-dependent mechanisms like mprotect* it's actually possible to mark a page of memory read-only so that attempts to write to it later generate segfaults, but that's a rather coarse and expensive operation that might not be worth it—the types-system-based protection could be sufficient in your use case.


mprotect is how program loaders usually implement write-protection for global data originally declared const: a linker will have aggregated all such data into a single contiguous block, the loader will load it, and then it will mprotect all of it as read-only in one system call.

Upvotes: 2

Related Questions