调用一个free()包装器:取消对类型输入指针的引用将打破严格的别名规则

时间:2022-08-14 02:14:41

I've tried to read up on the other questions here on SO with similar titles, but they are all a tiny bit too complex for me to be able to apply the solution (or even explanation) to my own issue, which seems to be of a simpler nature.

我已经试着读过这里的其他问题了,类似的题目,但它们都有点太复杂了,我无法将解决方案(甚至解释)应用到我自己的问题上,这似乎更简单。

In my case, I have a wrapper around free() which sets the pointer to NULL after freeing it:

在我的例子中,我有一个关于free()的包装器,它在释放指针后将指针设置为NULL:

void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}

In the project I'm working on, it is called like this:

在我正在进行的项目中,它的名字是这样的:

myfree((void **)&a);

This makes gcc (4.2.1 on OpenBSD) emit the warning "dereferencing type-punned pointer will break strict-aliasing rules" if I crank up the optimization level to -O3 and add -Wall (not otherwise).

这使得gcc (OpenBSD上的4.2.1)发出警告:“如果我将优化级别调高到-O3并添加-Wall(不是其他),那么取消引用类型punned指针将打破严格的别名规则”。

Calling myfree() the following way does not make the compiler emit that warning:

以以下方式调用myfree()并不会使编译器发出警告:

myfree((void *)&a);

And so I wonder if we ought to change the way we call myfree() to this instead.

所以我想知道我们是否应该改变我们叫myfree()的方式。

I believe that I'm invoking undefined behaviour with the first way of calling myfree(), but I haven't been able to wrap my head around why. Also, on all compilers that I have access to (clang and gcc), on all systems (OpenBSD, Mac OS X and Linux), this is the only compiler and system that actually gives me that warning (and I know emitting warnings is a nice optional).

我相信我是用调用myfree()的第一种方式调用未定义的行为,但是我还没弄明白为什么。此外,在所有的系统(OpenBSD、Mac OS X和Linux)上,我都可以访问(clang和gcc)的所有编译器,这是唯一的编译器和系统,它实际上给了我这个警告(而且我知道发出警告是一个很好的选择)。

Printing the value of the pointer before, inside and after the call to myfree(), with both ways of calling it, gives me identical results (but that may not mean anything if it's undefined behaviour):

在调用myfree()时,在调用myfree()之前、内部和之后,打印该指针的值,并使用两种方法调用它,给我相同的结果(但如果它是未定义的行为,这可能没有任何意义):

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

void myfree(void **ptr)
{
    printf("(in myfree) ptr = %p\n", *ptr);
    free(*ptr);
    *ptr = NULL;
}

int main(void)
{
    int *a, *b;

    a = malloc(100 * sizeof *a);
    b = malloc(100 * sizeof *b);

    printf("(before myfree) a = %p\n", (void *)a);
    printf("(before myfree) b = %p\n", (void *)b);

    myfree((void **)&a);  /* line 21 */
    myfree((void *)&b);

    printf("(after myfree) a = %p\n", (void *)a);
    printf("(after myfree) b = %p\n", (void *)b);

    return EXIT_SUCCESS;
}

Compiling and running it:

编译和运行:

$ cc -O3 -Wall free-test.c
free-test.c: In function 'main':
free-test.c:21: warning: dereferencing type-punned pointer will break strict-aliasing rules

$ ./a.out
(before myfree) a = 0x15f8fcf1d600
(before myfree) b = 0x15f876b27200
(in myfree) ptr = 0x15f8fcf1d600
(in myfree) ptr = 0x15f876b27200
(after myfree) a = 0x0
(after myfree) b = 0x0

I'd like to understand what is wrong with the first call to myfree() and I'd like to know if the second call is correct. Thanks.

我想知道第一个调用myfree()的调用有什么问题,我想知道第二个调用是否正确。谢谢。

3 个解决方案

#1


7  

Since a is an int* and not a void*, &a cannot be converted to a pointer to a void*. (Suppose void* were wider than a pointer to an integer, something which the C standard allows.) As a result, neither of your alternatives -- myfree((void**)a) and myfree((void*)a) -- is correct. (Casting to void* is not a strict aliasing issue. But it still leads to undefined behaviour.)

因为a是int*而不是void*,所以&a不能转换成指向void*的指针。(假设void*比指向整数的指针还要宽,这是C标准允许的)。因此,您的两个选项——myfree(void*)a和myfree(void*)a)都不是正确的。(强制转换为void*不是一个严格的混叠问题。但它仍会导致不明确的行为。

A better solution (imho) is to force the user to insert a visible assignment:

更好的解决方案(imho)是强制用户插入可见的赋值:

void* myfree(void* p) {
    free(p);
    return 0;
}

a = myfree(a);

With clang and gcc, you can use an attribute to indicate that the return value of my_free must be used, so that the compiler will warn you if you forget the assignment. Or you could use a macro:

使用clang和gcc,您可以使用一个属性来指示必须使用my_free的返回值,以便如果您忘记了赋值,编译器将警告您。或者你可以使用一个宏:

#define myfree(a) (a = myfree(a))

#2


3  

Here's a suggestion that:

这里有一个建议:

  1. Does not violate the strict aliasing rule.
  2. 不违反严格的混叠规则。
  3. Makes the call more natural.
  4. 让通话更自然。
void* myfree(void *ptr)
{
    free(ptr);
    return NULL;
}

#define MYFREE(ptr) ptr = myfree(ptr);

you can use the macro simply as:

您可以简单地将宏使用为:

int* a = malloc(sizeof(int)*10);

...

MYFREE(a);

#3


1  

There are basically a few ways to have a function work with and modify a pointer in a fashion agnostic to the pointer's target type:

基本上有几种方法可以让函数处理和修改指针,不受时尚的影响,指向指针的目标类型:

  1. Pass the pointer into the function as void* and return it as void*, applying appropriate conversions in both directions at the call site. This approach has the disadvantage of tying up the function's return value, precluding its use for other purposes, and also precludes the possibility of performing the pointer update within a lock.

    将指针作为void*传递到函数中,并将其作为void*返回,在调用站点的两个方向应用适当的转换。这种方法的缺点是将函数的返回值绑定在一起,从而不能将其用于其他目的,而且也不能在锁中执行指针更新。

  2. Pass a pointer to function which accepts two void*, casts one of them into a pointer of the appropriate type and the other to a double-indirect pointer of that type, and possibly a second function that can read a passed-in pointer as a void*, and use those functions to read and write the pointer in question. This should be 100% portable, but likely very inefficient.

    传递一个指针函数接受两个void *,将其中一个适当类型的指针,另一个double-indirect指针类型,并可能第二个函数可以读取一个传入指针作为一个void *,并使用这些函数来读和写指针。这应该是100%可移植的,但可能非常低效。

  3. Use pointer variables and fields of type void* elsewhere and cast them to real pointer types whenever they're actually used, thus allowing pointers of type void** to be used to modify the pointer variables.

    在其他地方使用指针变量和void*类型的字段,并在实际使用时将它们转换为真正的指针类型,从而允许使用void**类型的指针来修改指针变量。

  4. Use memcpy to read or modify pointers of unknown type, given double-indirect pointers which identify them.

    使用memcpy来读取或修改未知类型的指针,给定标识它们的双间接指针。

  5. Document that code is written in a dialect of C, popular in the 1990s, which treated "void**" as a double-indirect pointer to any type, and use compilers and/or settings that support that dialect. The C Standard allows for implementations to use different representations for pointers to things of different types, and because those implementations couldn't support a universal double-indirect pointer type, and because implementations which could easily allow void** to be used that way already did so before the Standard was written, there was no perceived need for the Standard to describe that behavior.

    使用C语言编写代码的文档,该语言在20世纪90年代很流行,它将“void*”视为指向任何类型的双间接指针,并使用支持该方言的编译器和/或设置。C标准允许实现使用不同的表示不同类型的指针的东西,因为这些实现不支持通用double-indirect指针类型,因为这可以很容易地实现允许void * *使用这种方式已经这样做之前标准写,没有感知标准来描述行为的必要性。

The ability to have a universal double-indirect pointer type was and is extremely useful on the 90%+ of implementations that could (and did) readily support it, and the authors of the Standard certainly knew that, but the authors were far less interested in describing behaviors that sensible compiler writers would support anyway, than in mandating behaviors which would be on the whole beneficial even on platforms where they could not be cheaply supported (e.g. mandating that even on a platform whose unsigned math instructions wrap mod 65535, a compiler must generate whatever code is needed to make calculations wrap mod 65536). I'm not sure why modern compiler writers fail to recognize that.

的能力有一个普遍double-indirect指针类型和极其有用90% +的实现(和)容易支持它,和作者的标准当然知道,但是作者不感兴趣描述行为,合理的编译器作者将支持,与强制行为相比,强制行为总体上是有益的,即使是在那些无法廉价支持它们的平台上(例如,强制要求即使是在一个没有签名的数学指令包装mod 65535的平台上,编译器也必须生成包装mod 65536的计算所需的任何代码)。我不知道为什么现代编译器作者没有认识到这一点。

Perhaps if programmers start overtly writing for sane dialects of C, the maintainers of standards might recognize that such dialects have value. [Note that from an aliasing perspective, treating void** as a universal double-indirect pointer will have far less severe performance costs than forcing programmers to use any of the alternatives 2-4 above; any claims by compiler writers that treating void** as a universal double-indirect pointer would kill performance should thus be treated skeptically].

也许,如果程序员开始公开地为正常的C语言编写代码,标准的维护人员可能会认识到这样的语言是有价值的。[注意,从混叠的角度来看,将void*作为一个通用的双间接指针要比强迫程序员使用上面任何一个2-4替代方法的性能成本要低得多;任何编译器作者声称将void**作为一个通用的双间接指针会扼杀性能的说法都应该被怀疑。

#1


7  

Since a is an int* and not a void*, &a cannot be converted to a pointer to a void*. (Suppose void* were wider than a pointer to an integer, something which the C standard allows.) As a result, neither of your alternatives -- myfree((void**)a) and myfree((void*)a) -- is correct. (Casting to void* is not a strict aliasing issue. But it still leads to undefined behaviour.)

因为a是int*而不是void*,所以&a不能转换成指向void*的指针。(假设void*比指向整数的指针还要宽,这是C标准允许的)。因此,您的两个选项——myfree(void*)a和myfree(void*)a)都不是正确的。(强制转换为void*不是一个严格的混叠问题。但它仍会导致不明确的行为。

A better solution (imho) is to force the user to insert a visible assignment:

更好的解决方案(imho)是强制用户插入可见的赋值:

void* myfree(void* p) {
    free(p);
    return 0;
}

a = myfree(a);

With clang and gcc, you can use an attribute to indicate that the return value of my_free must be used, so that the compiler will warn you if you forget the assignment. Or you could use a macro:

使用clang和gcc,您可以使用一个属性来指示必须使用my_free的返回值,以便如果您忘记了赋值,编译器将警告您。或者你可以使用一个宏:

#define myfree(a) (a = myfree(a))

#2


3  

Here's a suggestion that:

这里有一个建议:

  1. Does not violate the strict aliasing rule.
  2. 不违反严格的混叠规则。
  3. Makes the call more natural.
  4. 让通话更自然。
void* myfree(void *ptr)
{
    free(ptr);
    return NULL;
}

#define MYFREE(ptr) ptr = myfree(ptr);

you can use the macro simply as:

您可以简单地将宏使用为:

int* a = malloc(sizeof(int)*10);

...

MYFREE(a);

#3


1  

There are basically a few ways to have a function work with and modify a pointer in a fashion agnostic to the pointer's target type:

基本上有几种方法可以让函数处理和修改指针,不受时尚的影响,指向指针的目标类型:

  1. Pass the pointer into the function as void* and return it as void*, applying appropriate conversions in both directions at the call site. This approach has the disadvantage of tying up the function's return value, precluding its use for other purposes, and also precludes the possibility of performing the pointer update within a lock.

    将指针作为void*传递到函数中,并将其作为void*返回,在调用站点的两个方向应用适当的转换。这种方法的缺点是将函数的返回值绑定在一起,从而不能将其用于其他目的,而且也不能在锁中执行指针更新。

  2. Pass a pointer to function which accepts two void*, casts one of them into a pointer of the appropriate type and the other to a double-indirect pointer of that type, and possibly a second function that can read a passed-in pointer as a void*, and use those functions to read and write the pointer in question. This should be 100% portable, but likely very inefficient.

    传递一个指针函数接受两个void *,将其中一个适当类型的指针,另一个double-indirect指针类型,并可能第二个函数可以读取一个传入指针作为一个void *,并使用这些函数来读和写指针。这应该是100%可移植的,但可能非常低效。

  3. Use pointer variables and fields of type void* elsewhere and cast them to real pointer types whenever they're actually used, thus allowing pointers of type void** to be used to modify the pointer variables.

    在其他地方使用指针变量和void*类型的字段,并在实际使用时将它们转换为真正的指针类型,从而允许使用void**类型的指针来修改指针变量。

  4. Use memcpy to read or modify pointers of unknown type, given double-indirect pointers which identify them.

    使用memcpy来读取或修改未知类型的指针,给定标识它们的双间接指针。

  5. Document that code is written in a dialect of C, popular in the 1990s, which treated "void**" as a double-indirect pointer to any type, and use compilers and/or settings that support that dialect. The C Standard allows for implementations to use different representations for pointers to things of different types, and because those implementations couldn't support a universal double-indirect pointer type, and because implementations which could easily allow void** to be used that way already did so before the Standard was written, there was no perceived need for the Standard to describe that behavior.

    使用C语言编写代码的文档,该语言在20世纪90年代很流行,它将“void*”视为指向任何类型的双间接指针,并使用支持该方言的编译器和/或设置。C标准允许实现使用不同的表示不同类型的指针的东西,因为这些实现不支持通用double-indirect指针类型,因为这可以很容易地实现允许void * *使用这种方式已经这样做之前标准写,没有感知标准来描述行为的必要性。

The ability to have a universal double-indirect pointer type was and is extremely useful on the 90%+ of implementations that could (and did) readily support it, and the authors of the Standard certainly knew that, but the authors were far less interested in describing behaviors that sensible compiler writers would support anyway, than in mandating behaviors which would be on the whole beneficial even on platforms where they could not be cheaply supported (e.g. mandating that even on a platform whose unsigned math instructions wrap mod 65535, a compiler must generate whatever code is needed to make calculations wrap mod 65536). I'm not sure why modern compiler writers fail to recognize that.

的能力有一个普遍double-indirect指针类型和极其有用90% +的实现(和)容易支持它,和作者的标准当然知道,但是作者不感兴趣描述行为,合理的编译器作者将支持,与强制行为相比,强制行为总体上是有益的,即使是在那些无法廉价支持它们的平台上(例如,强制要求即使是在一个没有签名的数学指令包装mod 65535的平台上,编译器也必须生成包装mod 65536的计算所需的任何代码)。我不知道为什么现代编译器作者没有认识到这一点。

Perhaps if programmers start overtly writing for sane dialects of C, the maintainers of standards might recognize that such dialects have value. [Note that from an aliasing perspective, treating void** as a universal double-indirect pointer will have far less severe performance costs than forcing programmers to use any of the alternatives 2-4 above; any claims by compiler writers that treating void** as a universal double-indirect pointer would kill performance should thus be treated skeptically].

也许,如果程序员开始公开地为正常的C语言编写代码,标准的维护人员可能会认识到这样的语言是有价值的。[注意,从混叠的角度来看,将void*作为一个通用的双间接指针要比强迫程序员使用上面任何一个2-4替代方法的性能成本要低得多;任何编译器作者声称将void**作为一个通用的双间接指针会扼杀性能的说法都应该被怀疑。