C语言 强制转换函数指针 void 的效果是什么



所以我正在尝试第 64 次编写缓冲库,我开始研究一些非常高级的东西。以为我会要求对此提供一些专业意见。

在我的第一个头文件中,我有这个:

typedef struct StdBuffer { void* address; } StdBuffer;
extern void StdBufferClear(StdBuffer);

#includes第一个头文件的另一个头文件中,我有这个:

typedef struct CharBuffer { char* address; } CharBuffer;
void (*CharBufferClear)(CharBuffer) = (void*) StdBufferClear;

声明此函数指针 void 会干扰调用吗?它们具有按值签名匹配的内容。我以前从未见过声明为 void 的函数指针,但这是让它干净编译的唯一方法。

堆栈方面,它应该与我在汇编编码中学到的内容没有任何区别。

无关紧要的天哪!我刚刚在StackOverflow上说了Stackwise!

嗯。。看起来我在这里假设太多了。请允许我再次澄清,如果可以的话。我不在乎地址上存储了什么"类型"的数据。我所关心的只是一个"单位"的大小以及地址上有多少个单位。如果您愿意,请查看 API 的接口协议协定:

typedef struct StdBuffer {
    size_t width;        ///< The number of bytes that complete a data unit.
    size_t limit;        ///< The maximum number of data units that can be allocated for this buffer.
    void * address;      ///< The memory address for this buffer.
    size_t index;        ///< The current unit position indicator.
    size_t allocated;    ///< The current number of allocated addressable units.
    StdBufferFlags flags;///< The API contract for this buffer.
} StdBuffer;

你看,memcpy,memmove等并不真正关心地址上的内容,他们想要的只是我在这里清楚地跟踪的细节。

现在看看遵循此合同的第一个原型:

typedef struct CharBuffer {
    size_t width;        ///< The number of bytes that complete a data unit.
    size_t limit;        ///< The maximum number of data units that can be allocated for this buffer.
    char * address;      ///< The memory address for this buffer.
    size_t index;        ///< The current unit position indicator.
    size_t allocated;    ///< The current number of allocated addressable units.
    CharBufferFlags flags;///< The API contract for this buffer.
} CharBuffer;

正如您清楚地看到的那样,数据类型在此上下文中无关紧要。你可以说 C 根据情况以不同的方式处理它,但归根结底,只要我们在同一台机器上处理内存,address就是addressbytebytelonglong

当这个系统结合在一起时,其目的是消除所有基于这种类型的杂耍C似乎如此自豪(这是理所当然的......对于我想做的事情来说毫无意义。这是为位于任何地址的任何标准大小的数据(1,2,4,8,sizeof(RandomStruct))创建一个遵守合同的原型。

能够使用代码执行自己的转换,并使用 api 函数操作该数据,这些函数在具有特定长度内存单元的特定长度内存块上运行。但是,原型必须包含官方数据指针类型,因为最终用户每次想要使用该地址指针执行某些操作时都必须重新转换其数据是没有意义的。如果指针为空,则将其称为 CharBuffer 是没有意义的。

StdBuffer 是一种泛型类型,除了在 API 本身中之外,从未使用过,用于管理所有遵守契约的数据类型。

该系统将包含的 API 来自我最新版本的缓冲。这在代码@Google非常清楚地记录下来,我知道有些事情需要改变才能将这一切结合在一起,即如果没有大量的适当研究和意见收集,我将无法直接从 api 中安全地操作数据。

这让我注意到,我还需要在 StdBufferFlags 成员中有一个有符号/无符号位标志。

也许这个拼图的最后一块也是为了让你细读。

/** def BIT(I)
    brief A macro for setting a single constant bit.
 *
 *  This macro sets the bit indicated by I to enabled.
 *  param I the (1-based) index of the desired bit to set.
 */
 #define BIT(I) (1UL << (I - 1))
/** enum StdBufferFlags
    brief Flags that may be applied to all StdBuffer structures.
 *  These flags determine the contract of operations between the caller
 *  and the StdBuffer API for working with data. Bits 1-4 are for the
 *  API control functions. All other bits are undefined/don't care bits.
 *
 *  If your application would like to use the don't care bits, it would
 *  be smart not to use bits 5-8, as these may become used by the API
 *  in future revisions of the software.
*/
typedef enum StdBufferFlags {
    BUFFER_MALLOCD = BIT(1),    ///< The memory address specified by this buffer was allocated by an API
    BUFFER_WRITEABLE = BIT(2),  ///< Permission to modify buffer contents using the API
    BUFFER_READABLE = BIT(3),   ///< Permission to retrieve buffer contents using the API
    BUFFER_MOVABLE = BIT(4)     ///< Permission to resize or otherwise relocate buffer contents using the API
}StdBufferFlags;

此代码需要诊断:

void (*CharBufferClear)(CharBuffer) = (void*) StdBufferClear;

您正在将void *指针转换为不带强制转换的函数指针。在 C 语言中,void *指针可以转换为指向对象类型的指针,而无需强制转换,但不能转换为函数指针类型。(在C++中,还需要强制转换以将void *转换为对象类型,以提高安全性。

这里想要的只是在函数指针类型之间进行强制转换,即:

void (*CharBufferClear)(CharBuffer) = (void (*)(CharBuffer)) StdBufferClear;

然后你仍然在做相同类型的双关语,因为函数是不同的类型。您正在尝试使用指向CharBuffer函数的指针调用一个接受StdBuffer的函数。

这种类型的代码不是明确定义的 C。击败类型系统后,您只能依靠测试,检查目标代码,或者从编译器编写者那里获得一些保证,即这种事情可以与该编译器一起使用。

您在汇编程序编码中学到的知识不适用,因为汇编语言只有少量的基本数据类型,例如"机器地址"或"32 位字"。具有相同布局和低级表示的两个数据结构可能是不兼容类型的概念在汇编语言中不会出现。

即使两种类型在低级别看起来相同(另一个示例:unsigned intunsigned long有时完全相同),C 编译器也可以基于未违反类型规则的假设来优化程序。例如,假设AB指向相同的内存位置。如果将对象分配给A->member,则 C 编译器可以假定对象B->member不受此影响,如果 A->memberB->member 具有不兼容的类型,例如一个是char *的,另一个是void *的。生成的代码在寄存器中不断缓存旧的B->member值,即使内存中的副本被分配给A->member 覆盖。这是无效别名的示例。

该标准没有定义将函数指针强制转换为void *的结果。

同样,在函数指针之间进行转换,然后通过错误的指针调用也是未定义的行为。

有一些结构需要任何符合标准的 C 编译器一致地实现,还有一些

结构 99% 的 C 编译器确实一致地实现,但哪些符合标准的编译器可以自由地以不同的方式实现。 尝试将指针投射到采用一种指针的函数,转换为指向采用另一种指针的函数的指针,属于后一类。 尽管 C 标准规定void*char*的大小必须相同,但没有任何要求它们共享相同的位级存储格式,更不用说参数传递约定了。 虽然大多数机器允许以与单词大致相同的方式访问字节,但这种能力并不普遍。 应用程序二进制接口[指定如何将参数传递给例程的文档]的设计者可能会指定以最大化字节访问效率的方式传递char*,而void*应以最大化字访问效率的方式传递,同时保留保存未对齐字节地址的能力, 也许通过使用补充词来保持零或一来表示 LSB/MSB)。 在这样的计算机上,如果例程期望从期望传递char*的代码调用void*,则可能导致该例程访问任意错误数据。

不,使用哪种数据类型来存储数据并不重要。只有 C 类型用于读取和写入该数据,并且数据是否足够大小才重要。