【问题标题】:Check if a pointer to function is initialized检查指向函数的指针是否已初始化
【发布时间】:2018-04-10 22:38:43
【问题描述】:

如何检查指向函数的指针是否已初始化? 我可以检查 NULL,但如果不是 null 可能是垃圾,对吧?

我有以下几点:

#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;
}

如何检查这个指针是否真的指向函数“void (*f)(client *)”? 比较大小不起作用,因为可能会产生相同大小的垃圾,对吗? 我想要一种方法,最好根据 C 标准来完成。

【问题讨论】:

  • 不可能。您无法检测变量(any 变量、指针与否)是否已初始化。因此,如果您需要检查它们的有效性,请继续初始化所有指向 NULL 的指针。
  • 与任何变量相同:初始化为已知的“空”值,例如NULL,并对其进行测试。试图读取和解释未初始化的数据是徒劳的。
  • NULL 是使用指针时唯一的无效值。你应该是保护垃圾值的人,同样的情况对于普通指针也是如此,如果你放垃圾你就无法知道它是垃圾还是有效值。
  • 故事的寓意:不要让客户自己初始化字段。提供一个执行此操作的函数,以便他们使用一些参数调用(或不使用,默认初始化所有内容)。
  • 并且不要强制返回 malloc !写client *c = malloc(sizeof *c);

标签: c function-pointers


【解决方案1】:

如 cmets 中所述,不可能 100% 确定指针是否为垃圾。

为了避免这种情况,你可以提供一个“构造函数”,像这样:

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;
}

然后在您的文档中写下创建“客户”的唯一有效方法是使用您的函数。如果你这样做,你还应该提供一个destroy 函数,你可以在其中调用free

假设有一天您添加了一个指向您的struct 的新指针。然后更新client_allocate 函数,将指针设置为NULL,新指针将始终正确初始化。无需更新代码中分配了struct 的所有位置,因为现在只有一个这样的位置。

【讨论】:

    【解决方案2】:

    注意事项

    检查指向函数的指针是否使用有效函数初始化并不是一个容易解决的问题。任何解决方案都将跨平台移植,并且还取决于您最终得到的二进制格式(静态或动态可链接格式)。有多种方法可以在不同的二进制格式上成功地做到这一点,但是我不会详细介绍每一种排列方式。希望这会让您陷入困境:-),并且您可以找出适合您情况的特定解决方案。

    为了使某些解决方案起作用,您必须确保链接的二进制文件已导出符号(可以不这样做,但要困难得多,而且我没有时间)。因此,当您链接您的程序时,请确保您启用了动态符号。

    话虽如此,您可以在使用 dlfcn 函数的系统上使用这种方法。 (见下面的历史)

    更多注意事项

    正如@Deduplicator 在下面的评论中指出的那样,在某些情况下,0xdeadbeef 可能会随意指向一个有效的函数,在这种情况下,您最终可能会遇到调用 的情况错误 有效功能。有一些方法可以在编译时或运行时缓解这种情况,但您必须手动构建解决方案。例如,C++ 通过将命名空间中的符号改造成符号来做到这一点。你可以要求发生这种情况。 (我会想一个有趣的方法来做这个并发布它)

    Linux / SysV 变体(包括 Mac OSX)

    使用dladdr (SysV)(GNU 也有 dladdr1)来确定您提供的地址属于哪个函数

    示例:

    #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;
    }
    

    注意启用动态符号以使其工作

    GCC/LLVM 等

    在链接过程中使用-rdynamic-Wl,--export-dynamic,所以编译时使用:

    gcc -o ex1 -rdynamic ex1.c

    窗口

    Windows 做自己的事情(一如既往),我没有测试过这些,但基本概念应该可以工作:

    一起使用GetModuleHandleEnumCurrentProcess 来获取加载的符号信息并在循环中遍历指针以查看它们与其中的任何地址匹配。

    另一种方法是使用VirtualQuery,然后将mbi.AllocationBase 转换为(HMODULE),看看你是否得到了你自己的二进制文件的路径。

    【讨论】:

    • 如果指针的不确定值恰好是一个有效的符号地址怎么办?如果编译器优化使 UB 中断怎么办?这只是一个超级复杂的地雷。
    • 假设输入可能不确定而不是任意不匹配不会妨碍您,您能否补充一下答案的准确性?
    • @Deduplicator 我会为这种效果添加一点,但我们处于 C 世界,最后一切都是程序员的责任。恕我直言,创建超出答案范围的有效性范围。
    • 你可以在这里打印出函数名和内容 不一定。见stackoverflow.com/questions/11731229/…
    • 有人嫉妒?
    【解决方案3】:

    在 c 中,函数指针与常规指针没有什么不同,按照标准,它们有一个值表示不应使用该值,即 NULL

    您应该使用指针的方式是将它们仅设置为有效值或NULL。没有其他方法可以确保存在 OK 值。根据定义,每个不是NULL 的值都应该被认为是有效的。

    【讨论】:

    • C 语言中(大部分)没有默认初始化。 Stack 和 Heap-Variables 可以包含任何内容。只有全局/静态变量被零初始化(如果没有显式初始化)。并且编译器运行时可能会设计为将默认模式写入内存以便于调试。
    • 并且首选的方法是在创建时初始化它们(指向NULL 用于指针),以便有随机值,稍后如果需要将它们设置为有意义的值或者如果存在不应该的值使用将它们设置回NULL
    【解决方案4】:

    就像在其他 cmets 和答案中指出的那样,没有办法检查变量是否已初始化。这就是为什么将 vars 初始化为 NULL 然后检查被认为是好的做法。

    如果您真的想验证您的函数指针是否指向正确的位置,您可以导出函数并从 ELF 符号加载您的指针(请参阅:http://gcc.gnu.org/wiki/Visibility

    【讨论】:

      【解决方案5】:

      始终首先检查空参数。

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

      至于您的问题,请在 malloc 后将您的客户端结构无效。这样,您可以确保未分配的函数指针确实 ==NULL。

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

      【讨论】:

      • 这并没有解决检查函数指针是否有效的 OP 问题。
      • 请不要写这样的代码。如果出现错误并且出现空参数,我宁愿立即进行调试中断,而不是在完全不同的地方发生无声故障。
      • @Quentin 我不明白你的意思。如果 malloc 失败,create_client 函数将返回 NULL,并且调用它的任何函数也应该进行 NULL 检查。
      • @Quentin 我只是通过指出它应该是 NULL 检查来使 OP 的功能更安全。指出程序流程本身并不是重点。就我个人而言,我总是有一个返回值来表示成功/失败。
      猜你喜欢
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-09
      • 2015-04-28
      • 1970-01-01
      • 2011-07-08
      • 1970-01-01
      相关资源
      最近更新 更多