1

How can I check if a pointer to function was initialized? I can check for NULL, but if not null could be garbage, right?

I have the following:

#include <stdio.h>
#include <stdlib.h>

typedef struct client_struct
{
    char *name;
    char *email;
    void (*print)(struct client_struct *c);
} client;

void print_client(client *c)
{

    if (c->print != NULL)
        c->print(c);
}

int main()
{
    client *c = (client *)malloc(sizeof(client));
    c->email = (char *)malloc(50 * sizeof(char));
    sprintf(c->email, "email@server.com");
    c->name = (char *)malloc(50 * sizeof(char));
    sprintf(c->name, "some name");

    //Uncommenting line below work as expected, otherwise segmentation fault
    //c->print = NULL;

    print_client(c);

    printf("\nEOF\n");
    int xXx = getchar();

    return 0;
}

How can I check if this pointer really points to function "void (*f)(client *)"? Comparing size doesn't work because could garbage in same size, correct? I would like a way to accomplish that preferably according to C standard.

ramires.cabral
  • 910
  • 4
  • 13
  • 28
  • 9
    It's not possible. You can not detect if a variable (*any* variable, pointer or not) is initialized or not. So keep initializing all pointers to `NULL` if you need to check them for validity. – Some programmer dude Apr 10 '18 at 12:12
  • 4
    Same as with any variable: initialize to a known "empty" value such as `NULL`, and test for that. Trying to read and interpret uninitialized data is futile. – Quentin Apr 10 '18 at 12:12
  • 1
    ````NULL```` is the only invalid value when working with pointers. You should be the one that is protecting the value of being garbage, the same situation is true with regular pointers, if you put garbage you cannot know if it is garbage or valid value. – anakin Apr 10 '18 at 12:14
  • 3
    Moral of the story: don't let clients initialize the fields by themselves. Supply a function that does it, for them to call with some arguments (or without, to default initialize everything). – StoryTeller - Unslander Monica Apr 10 '18 at 12:14
  • 4
    and don't cast the return of malloc ! write `client *c = malloc(sizeof *c);` – Stargateur Apr 10 '18 at 12:15
  • @Stargateur I'm using MinGW and it warns to perform that cast... Why not cast? – ramires.cabral Apr 10 '18 at 12:23
  • 1
    @ramires.cabral That surprise me a lot, maybe you are compiling with c++, whatever in C, [Do I cast the result of malloc?](https://stackoverflow.com/a/605858/7076153) => no. – Stargateur Apr 10 '18 at 12:28
  • 1
    @ramires.cabral You are probably tring to compile C code with a C++ compiler. That doesn't work. – Quentin Apr 10 '18 at 12:28
  • @Quentin You're right! I'm using g++... – ramires.cabral Apr 10 '18 at 12:29
  • Possible duplicate of [How to check if a variable has been initialized in C?](https://stackoverflow.com/q/36490948/1275169) – P.P Apr 10 '18 at 12:30
  • @ramires.cabral I recommend `gcc -std=c11 -Wall -Wextra -pedantic-errors` as your flags. That will compile standard C and catch a load of issues. – Quentin Apr 10 '18 at 12:31
  • @Quentin I'll try that, thanks! – ramires.cabral Apr 10 '18 at 12:32
  • @Quentin The errors-part is debatable, but enforcing the necessary discipline when starting a green-field project is probably a good idea. – Deduplicator Apr 10 '18 at 13:13
  • @Deduplicator strictly conforming to the standard is a good thing in my book. – Quentin Apr 10 '18 at 13:22
  • @P.P. I don't think it's a dup. I'm asking about pointers, specifically function pointers. That question is about variables. – ramires.cabral Apr 10 '18 at 13:34
  • 1
    @ramires.cabral A pointer variable *is* a variable - it's not anything special with respect to whether it's been initialized or not. – P.P Apr 10 '18 at 13:38

5 Answers5

1

As described in the comments, it is impossible to determine with 100% certainty whether a pointer is garbage.

To avoid such situation, you can provide a "constructor" function, like this:

struct client_struct* client_allocate()
{
    struct client_struct* object = malloc(sizeof *object);
    if (object)
    {
        object->name = NULL;
        object->email = NULL;
        object->print = NULL;
    }
    return object;
}

Then write in your documentation that the only valid way to create "clients" is by using your function. If you do this, you should also provide a destroy function, where you call free.

Suppose you add a new pointer to your struct one day. Then you update your client_allocate function, where you set this pointer to NULL, and the new pointer will always be properly initialized. There is no need to update all places in code where your struct is allocated, because now there is only one such place.

anatolyg
  • 26,506
  • 9
  • 60
  • 134
0

In c function pointers are no different than regular pointers and by standard they have one value that says the value should not be used and this is NULL.

The way you should work with pointers is to set them only to valid value or NULL. There is no other way you can be sure there is a OK value. And by definition every value that is not NULL should be considered valid.

anakin
  • 551
  • 4
  • 12
  • There is (mostly) no default initialization in the C language. Stack and Heap-Variables can contain any content. Only global/static variables are zero-initialized (if not explicitly initialized). And compilers runtime may be designed in a way that a default pattern is written to memory for easier debugging. – vlad_tepesch Apr 10 '18 at 12:36
  • And the preferred way is to initialize them(to ````NULL```` for pointers) on creation so there is random values and later if needed set them to meaningful value or if there was a value that should not be used set them back to ````NULL````. – anakin Apr 10 '18 at 12:39
0

Like pointed to in other comments and answers, there is not way to check a variable is initialized. That's why initializing vars to NULL and then checking is considered good practice.

If you really want to validate your function pointer is pointing to the correct place, you could export the function and load your pointer from the ELF symbols (see: http://gcc.gnu.org/wiki/Visibility)

polo
  • 1,352
  • 2
  • 16
  • 35
0

Caveats

Checking if a pointer to a function is initialized with an valid function is not an easily solvable problem. Any solution, will not be portable across platforms, and is also dependent on the binary format (statically or dynamically linkable formats) that you end up with. There are ways to do this, with varying success, on different binary formats, however I am not going to go over every permutation. Hopefully this will get you going down that rabbit hole :-) and you can figure out the particular solution that works for you in your circumstances.

In order for some of the solutions to work you have to ensure that the linked binaries have exported symbols (it's possible to do it without, but it's a lot harder and I don't have the time). So when you're linking your program ensure that you have dynamic symbols enabled.

Having said that, here's an approach you can use on systems using dlfcn functions. (See History below)

More Caveats

As @Deduplicator points out in his comment below, there may be situations where 0xdeadbeef may arbitrarily happen to point to a valid function, in which case you may end up with a situation where you end up calling the wrong valid function. There are ways to mitigate that situation at either compile-time or runtime but you'll have to build the solution by hand. For example, C++ does it by mangling in namespace into the symbols. You could require that to happen. (I'll think of an interesting way to do this and post it)

Linux / SysV variants (Mac OSX included)

Use dladdr (SysV) (GNU has a dladdr1 as well) to determine which function does the address you provide fall within:

Example:

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


int is_valid_function_ptr( void *func)  {   
      Dl_info info;  
      int rc;      
    
      rc = dladdr(func, &info);   
    
      if (!rc) {
          /* really should do more checking here */
          return 0;
      }  

      return 1; /* you can print out function names and stuff here */    
}

void print(const char *value) {
    fprintf(stdout, "%s", value);
}

void call_the_printer(void (*foo)(), const char *value)
{
    if(is_valid_function_ptr(foo)) {
        foo(value);
    }
    else {
        fprintf(stderr, "The Beef is Dead!\n");
    }
}

int main() 
{
    void (*funcptr)() = (void (*)()) 0xdeadbeef; /* some dead pointer */
    call_the_printer(funcptr, "Hello Dead Beef\n");
    funcptr = print; /* actually a function */
    call_the_printer(funcptr, "Hello Printer\n");
    return 0;
}

NOTE Enable dynamic symbols for this to work

GCC/LLVM etc.

use -rdynamic or -Wl,--export-dynamic during the link process, so compile with:

gcc -o ex1 -rdynamic ex1.c

Windows

Windows does its own thing (as always) and I haven't tested any of these, but the basic concept should work:

Use GetModuleHandle and EnumCurrentProcess together to get loaded symbol information and run through the pointers in a loop to see they match any of the address therein.

The other way would be to use VirtualQuery and then cast mbi.AllocationBase to (HMODULE) and see if you get the path of your own binary back.

Community
  • 1
  • 1
Ahmed Masud
  • 21,655
  • 3
  • 33
  • 58
  • What if the pointer's indeterminate value happens to be a valid symbol address? What if compiler optimizations make that UB break? This is just a super-convoluted landmine. – Quentin Apr 10 '18 at 13:45
  • 1
    Could you add how accurate the answer will be, assuming the input being possibly indeterminate instead of just arbitrary non-matching doesn't get in the way? – Deduplicator Apr 10 '18 at 14:01
  • @Deduplicator I'll add a point to that effect, but we are in the *C world* and everything is the programmer's responsibility at the end. Creating validity bounds IMHO beyond the scope of the answer. – Ahmed Masud Apr 10 '18 at 14:19
  • 1
    *you can print out function names and stuff here* Not necessarily. See https://stackoverflow.com/questions/11731229/dladdr-doesnt-return-the-function-name – Andrew Henle Apr 10 '18 at 14:45
  • @AndrewHenle true -- caveats abound – Ahmed Masud Apr 10 '18 at 15:04
  • 1
    Someone being jealous? – anatolyg Apr 10 '18 at 23:31
  • I downvoted for the reasons I listed above. This relies on undefined behaviour (interpreting the value of uninitialized data) to replace a trivial null-check with a heavyweight symbol table lookup. It also suffers from false positives, since `dladdr` succeeds as soon as your rogue pointer happens to point into a shared object. Finally, it requires toolchain configuration to export dynamic symbols. When the normal solution is "just initialize the dang thing like you should be doing anyway", this is insane. – Quentin Apr 11 '18 at 08:17
  • @quentin never said it wasn't insane. But it *actually* answers the OP question without passing judgment on whether or not it is a good pattern. I cannot get into his mind to figure out what his actual use case is, Can you? This just introduces the OP to possibilities, albeit on the surface esoteric ones. Nothing wrong with that. Especially when you get into the realm of function pointers in C. If you're going to go there, you might as well go there. – Ahmed Masud Apr 11 '18 at 16:42
  • It's not "judgement", it's acknowledging a XY problem. If the solution OP is trying to implement requires breaking language rules, starting an arms race with the optimizer and invoking help from platform details, while not even being guaranteed to work in the best case, then clearly it is not the correct solution. OP's problem is "I cannot be bothered to initialize my objects", and C's response to that is "tough luck, you have to", period. – Quentin Apr 13 '18 at 11:13
  • That's not C's response. The compiler never says you better initialize me. It just says "well okay then..." And goes along. – Ahmed Masud Apr 13 '18 at 16:25
  • @AhmedMasud since you seem to see nothing wrong with what you just described, [here](https://godbolt.org/g/5QbHrT) is a demo of three compilers "going along". GCC doesn't optimize and "works" as you'd expect, Clang 3.0 triggers a crash *if* your pointer is valid, and Clang 3.1 will never call your callback even if the pointer is valid. C is an international specification, which says you must not use uninitialized values. If you break the specification, then your code doesn't work, regardless of what *you* think should be the proper behaviour. – Quentin Apr 16 '18 at 08:10
  • @Quentin you know, at first I was impressed that you'd taken the time, but now I am a disappointed at your attempt to be deceptive just to try make a point. In particular, the use of -O3 suppresses the call of an empty function in clang. Of course in your example "it will never be called" because you don't actually have a body to the function. Study this: https://godbolt.org/g/WkRwcK; also, I never said that the example above conforms to "C" standards. A lot of very valid projects don't. Something for you to contemplate after you delete your account, EOD. – Ahmed Masud Apr 16 '18 at 10:29
  • @AhmedMasud the function is not empty, it's undefined. I left it that way *precisely* so the optimizer wouldn't inline it and leave the `call isValidFptr` instruction intact. What is optimized out is `call rdi`, the actual function pointer call. And I'm sorry you can't tolerate people who like their projects to not blow up after a compiler upgrade, but I'm not planning on deleting my account. – Quentin Apr 16 '18 at 11:27
-1

Always check for null parameters first of all.

void print_client(client *c) 
{
    if ((c != NULL) && (c->print != NULL)) 
    {
        c->print(c);
    }   
}

As for your question, nullify your client struct after it's been malloc'd. This way you can ensure that an unassigned function pointer shall indeed ==NULL.

client* create_client(void)
{
    client *c = malloc(sizeof(client));
    if (c != NULL)
    {
        memset(c, 0, sizeof(c))
    }

    return c;
}
John Go-Soco
  • 886
  • 1
  • 9
  • 20
  • This does not address the OP question of checking whether or not a function pointer is valid. – Ahmed Masud Apr 10 '18 at 12:23
  • 1
    Please don't write such code. If there's a bug and a null parameter appears, I'd much rather have an immediate debugbreak than a silent failure which blows up in an entirely different place. – Quentin Apr 10 '18 at 12:30
  • @Quentin I don't see what you mean. The create_client function will return a NULL in the event that malloc fails, and whatever function calling it should be NULL-checking as well. – John Go-Soco Apr 10 '18 at 14:30
  • @JohnGo-Soco I'm talking about `print_client`: these checks will just make the function fail silently when it should be reporting an error or blow up to be debugged. – Quentin Apr 10 '18 at 14:33
  • @Quentin I was merely making the OP's function safer by pointing out it should be NULL-checking. Pointing out the program flow itself was not the point. Personally I would always have a return value to denote success/failure. – John Go-Soco Apr 10 '18 at 14:36