-1

Is it possible somehow to check at compile time or at run time that variable has static storage-class specifier?

Example:

static int v1;
int        v2;

bool r1 = is_static(v1); /* true */
bool r2 = is_static(v2); /* false */
pmor
  • 5,392
  • 4
  • 17
  • 36

2 Answers2

2

Disclaimer: The following code is not standards compliant, portable, or even safe. Please don't use it in production.

We can determine whether a variable is static by checking whether it is on the stack. Out function is_static() takes two arguments. The first is a pointer to our variable that we want to check and the second is a pointer to the beginning of the stack (that's what a is for). It then uses getrlimit() to determine the location of the end of that stack and checks whether our variable is on the stack. This should work on most unix-like systems.

#include <stdio.h>
#include <sys/resource.h>

_Bool is_static(void* var, char* stack_start)
{
  struct rlimit stack; // Use getrlimit to get the maximum stack size.
  getrlimit(RLIMIT_STACK, &stack); // TODO only run this once.
  char* stack_end = stack_start - stack.rlim_cur; // Work out where the end of the stack is.
  return !((char*)var < stack_start && (char*)var > stack_end) // Return whether the variable is on the heap.
}

int main()
{
  char a = 0;

  int b = 42;
  static int c = 42;

  printf("is_static(b) returns %i\n", is_static(&b, &a));
  printf("is_static(c) returns %i\n", is_static(&c, &a));
}

We run the code and get this:

is_static(b) returns 0
is_static(c) returns 1

EDIT: If you don't want to use getrlimit(), then you could use __builtin_return_address() to determine the highest stack value.

  • 1
    Interesting, thanks! This approach reminds me the `fill()-boundlow()-studyit()` approach used in this code: http://homepage.divms.uiowa.edu/~jones/opsys/threads/source.txt. See `thread_manager_init()`. – pmor Jan 01 '21 at 13:02
  • 1
    Btw, if `int c = 42` is defined as global non-static variable, then `is_static(&c, &a)` returns `true`, which is wrong because `c` does not have `static` storage-class specifier: https://godbolt.org/z/rza4Y8. Any ideas? – pmor Jan 06 '21 at 00:46
  • Global variables are not allocated on the stack, so ```is_static()``` will return true for global variables. Globals and static variables may be stored in different places, and it may be possible to differentiate between them at runtime, but im not sure how to. I guess ```is_stack_allocated()``` would be a better name for the function. – StavromulaBeta Jan 07 '21 at 08:12
  • 1
    @chux - Reinstate Monica Any ideas? – pmor Jan 10 '21 at 22:37
  • I'm pretty sure this is impossible at runtime. I've looked it up and static globals are stored in basically the same place as non-static globals, so we can't reliably just check their addresses, and compiler optimisations will likely move them around anyways. At compile time, this may be possible. – StavromulaBeta Jan 12 '21 at 08:33
  • Ok, it is possible. – StavromulaBeta Jan 12 '21 at 09:27
1

Ok, turns out that my previous answer didn't work for static global variables, so I have devised this program that can check whether a global variable is declared static. The way it works is that dysym() can't find static globals in the symbol table, so I just check it's output. It also references the variable with a boolean and, to ensure it actually exists.

#include <stdio.h>
#include <dlfcn.h>

char* a = "hello, world";
static char* b = "hello, world";

#define is_static(name) \
  (is_sym_static(#name) && &name)

_Bool is_sym_static(const char* const name)
{
  void* hdl = dlopen(NULL, 0); // TODO: optimise by only calling this once.
  return dlsym(hdl, name) == NULL;
}

int main(int argc, char** argv)
{
  printf("%i\n", is_static(a)); // prints 0
  printf("%i\n", is_static(b)); // prints 1
}

This must be compiled with -ldl -Wl,--export-dynamic, to ensure that all variables end up in the symbol table. This doesn't work for local variables, but we can combine it with my previous answer and...

#include <stdio.h>
#include <dlfcn.h>
#include <sys/resource.h>

static char* stack_start;

#define is_static(name) \
  (is_addr_static(&name) && is_sym_static(#name) && &name)

_Bool is_sym_static(const char* const name)
{
  void* hdl = dlopen(NULL, 0); // TODO: optimise by only calling this once.
  return dlsym(hdl, name) == NULL;
}

_Bool is_addr_static(void* var)
{
  struct rlimit stack;
  getrlimit(RLIMIT_STACK, &stack); // TODO: optimise by only calling this once.
  char* stack_end = stack_start - stack.rlim_cur;
  return !((char*)var < stack_start && (char*)var > stack_end);
}

char*        a = "hello, world";
static char* b = "hello, world";

int main(int argc, char** argv)
{
  char _;
  stack_start = &_;
  char*        c = "hello, world";
  static char* d = "hello, world";
  printf("%i\n", is_static(a)); // prints 0
  printf("%i\n", is_static(b)); // prints 1
  printf("%i\n", is_static(c)); // prints 0
  printf("%i\n", is_static(d)); // prints 1
}

This now detects both global and local static variables. I see no reason to need this however, since local static and global static are fundamentally different things.