【问题标题】:Incompatible pointer type warning with pointer-to-pointer types when passing char** to void** function parameter将 char** 传递给 void** 函数参数时,与指针类型不兼容的指针类型警告
【发布时间】:2021-06-29 05:05:22
【问题描述】:

我正在尝试实现一个安全释放函数,它擦除分配的内存,释放它,然后还将指向分配区域的指针设置为 NULL,因此指针不能在释放后重用,也不能被双重释放相同的功能。为了实现这一点,我使用了一个指向指针的参数,它允许我覆盖指向已分配内存的指针。

问题是 GCC 抱怨指针类型不兼容(“但它在我的机器上工作”);我没想到会有这样的警告。我的理解是任何指针都可以隐式转换为void*,因此我猜测指针的地址也可以转换为void**

与此同时,我将secure_free() 重写为一个宏,这解决了警告,但我想知道编译器为什么会抱怨。

文件securefree.c

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define STRING_BUFFER_LEN 10

/**
 * Securely erases a heap-allocated memory section, frees it and sets its
 * pointer to NULL to avoid use-after-free and double-free.
 */
static void secure_free(void** p_p_data, size_t length_in_bytes)
{
    if (p_p_data == NULL || *p_p_data == NULL)
    { return; }
    memset(*p_p_data, 0, length_in_bytes);
    free(*p_p_data);
    *p_p_data = NULL;
}

int main(void)
{
    // Allocate some data
    char* my_string = calloc(STRING_BUFFER_LEN, sizeof(char));
    if (my_string == NULL) { return 1; }
    // Use the allocated space in some way
    my_string[0] = 'a';
    my_string[1] = 'b';
    // Free using the dedicated function
    secure_free(&my_string, STRING_BUFFER_LEN);
    return 0;
}

使用 GCC 编译(Rev6,由 MSYS2 项目构建,10.2.0):

$ gcc securefree.c -o securefree
securefree.c: In function 'main':
securefree.c:29:17: warning: passing argument 1 of 'secure_free' from incompatible pointer type [-Wincompatible-pointer-types]
   29 |     secure_free(&my_string, STRING_BUFFER_LEN);
      |                 ^~~~~~~~~~
      |                 |
      |                 char **
securefree.c:11:32: note: expected 'void **' but argument is of type 'char **'
   11 | static void secure_free(void** p_p_data, size_t length_in_bytes)
      |                         ~~~~~~~^~~~~~~~

编辑:宏版本如下所示

#define secure_free_macro(ptr, len) if ((ptr) != NULL) { \
        memset((ptr), 0, (len)); free(ptr); (ptr) = NULL; }

【问题讨论】:

    标签: c pointers void-pointers gcc-warning pointer-to-pointer


    【解决方案1】:

    你试图做的事情不能移植,因为不同的指针类型可以有不同的表示;并且要将空指针分配给该值,您必须将指针指针 first 转换为指向实际指针变量的 有效类型 的指针 - 这是不可能的.

    但是你可以做的是使用宏,它和其他宏一样好,而且使用起来更简单:

    #define secure_free(x) (free(x), (x) = 0)
    

    这在没有&amp; 的情况下有效。

    【讨论】:

    • AFAIK 总是可以将 void* 变量设置为 NULL:它实际上是将指针数据类型(因此是地址)设置为零。这是可移植的,因为它可以在不同的机器上编译成不同的指针大小,这就是 void* 的用途 - 还是我完全误解了你的答案?
    • @Matjaž:您可以将任何指针对象设置为空指针。但是,如果您有一个 void * 参数 p 是从 int ** 参数传递的,并尝试使用 * (void **) p = 0; 将其设置为 null,则不会发生这种情况。参数p传递的地址是int *的地址,而不是void *的地址,它们是不同的类型,在内存中可能有不同的表示和大小。 * (void **) p = 0; 将尝试将内存中 void * 的字节设置为空指针,但这可能不适用于 int * 的字节。
    • @Matjaž,见port70.net/~nsz/c/c11/n1570.html#6.2.5p28。看起来只有void* 和指向字符的指针是可以互换的。此外,*(void**)p=0 可能违反严格的别名规则,但可以使用memcpy 解决。
    • @Matjaž:虽然这样的机器在很大程度上是过时的,但在现代编译器中存在一个后果:不同的指针类型通常不兼容,如 C 标准定义的那样,将一种指针类型作为另一种访问违反了别名规则在 C 2018 6.5 7 中,即使大小和表示相同。这使得执行此操作的代码的行为未由 C 标准定义,这意味着编译器优化可能会以破坏程序的方式对其进行转换。如果没有编译器的额外保证,则不应使用以这种方式使用错误类型的代码。
    【解决方案2】:

    C 允许将任何指针隐式 强制转换为void* 作为显式异常。请注意,voidchar兼容的类型。因此void*char*void**char** 也不兼容。这就是编译器发出警告的原因。

    要绕过此问题,请将函数签名更改为使用void*

    void secure_free(void* ptr, size_t length_in_bytes) {
       void **p_p_data = (void**)ptr;
       ...
    }
    

    要添加对参数是指向指针的指针的保护,可以使用宏:

    #define secure_free(x,s) ((void)sizeof **(x), secure_free((x), (s)))
    
    • 如果 x 不是指向指针的指针,则表达式 **(x) 将无法编译。
    • sizeof 防止在 **(x) 中评估 x 以避免副作用
    • (void) 让编译器停止抱怨未使用的值
    • 逗号运算符(X,Y),只返回Y的值,也就是secure_free(...)的返回值
    • 使用与函数相同的宏名称允许将secure_free 扩展为宏,仅当它用作函数时。这允许使用secure_free 作为指向函数的指针

    补充说明。在代码中

        memset(*p_p_data, 0, length_in_bytes);
        free(*p_p_data);
    

    编译器可能会优化出memset()。我建议强制转换为volatile void * 以强制编译器生成清除代码。

    编辑

    此外,由于memset 丢弃了volatile 限定符,因此可能会使用循环清除内容。

    【讨论】:

    • 这可行,但我失去了类型安全性:函数调用者现在可以传递指针而不是指针的地址,而不会发出任何编译器警告:secure_free(my_string)secure_free(&amp;my_string) 在编译时都没有警告只有第二个是正确的。
    • 谢谢!您解释了编译器警告的原因并提出了解决方案。但是,如果我必须编写一个函数,然后将其包装在一个宏中,为了简单起见,我只会使用宏版本 secure_free_macro()。关于 volatile 演员表:这是一个很好的建议,但是`警告:传递 'memset' 的参数 1 会丢弃 'volatile' 限定符` PS:downvote 不是我的。
    • secure_free被传递给void指针或字符类型以外的指针类型的地址时,这个指针被void **p_p_data = (void **) ptr;转换为void **,然后@987654353 @ 具有未定义的行为,因为它违反了 C 2018 6.5 7,即只能使用与其有效类型或某些其他类型兼容的类型来访问(包括写入)对象。 *p_p_data的类型是void *,但是它指向的对象是不兼容的类型,比如int *,所以违反了规则。所以这个代码不应该被使用。
    • 参考C标准加分
    • @EricPostpischil,猜想memcpy(ptr, &amp;(void*){0}, sizeof(void*)) 可以绕过严格别名的问题。但是 C 标准仍然不能保证原始指针会正确设置为 NULL。
    猜你喜欢
    • 1970-01-01
    • 2015-03-08
    • 1970-01-01
    • 2018-05-23
    • 2021-12-11
    • 1970-01-01
    • 2011-01-10
    • 2020-07-29
    • 2016-11-23
    相关资源
    最近更新 更多