将具有 Void 指针的结构转换为具有类型指针的结构

Casting Structs With Void Pointers into Structs With Typed Pointers

简短版本:

假设我有两个结构:

struct charPtrWithLen
{
 size_t len;
 char * charPtr;
}

struct voidPtrWithLen
{
 size_t len;
 void * voidPtr;
}

有没有一种方法可以将 voidPtrWithLen 转换为 charPtrWithLen,反之亦然,或者更好的方法是将一个转换为另一个,就像 char * 和 void * 可以轻松转换和隐式转换一样彼此?

换句话说:

我正在尝试编写我所有的 C,以便所有指向数组的指针都带有它们的大小信息。我还尝试在适用的情况下使用 void 指针编写通用函数,以保持本质上相同,嗯,相同的操作。我正在寻找一种方法将包含类型化指针的 'sized-array' 结构传递到采用包含空指针的 'sized-array' 参数的通用函数中。

长版,包含示例:

所以,void 指针非常灵活,所以我可以这样做:

int foo(void * ptr, size_t dataLen);
/* ... */
char * c;
size_t c_n;
/* ... */
foo(c, c_n);
/* ... */
int * i;
size_t i_n;
/* ... */
foo(i, i_n);

但是由于 "pointer to arbitrary length array, plus size there-of" 的模式如此普遍,假设在某个时候我厌倦了根据成对的参数、指针和长度来指定我的各种函数,而是开始用这样的代码改为封装在结构中的对:

typedef struct
{
    size_t v_n;
    void * v;
}
pointerWithSize;
/* ... */
int foo(pointerWithSize);

到目前为止一切顺利。我总是可以轻松地将我的 "char * c" 或 "int * i" 分配给 pointerWithSize 的 "void * v"。但是当你这样做足够长的时间,使用相同的模式时,你 运行 会遇到以下问题:很快你就会有一堆通用函数,它们与数据无关,因此很乐意采用 void 指针,因为例如:

pointerWithSize combinePointersWithSize(pointerWithSize p1, pointerWithSize p2);
int readFromStream(FILE * readFromHere, pointerWithSize * readIntoHere);

但是您最终也会得到一些固有地用于特定数据类型的函数:

size_t countOccurancesOfChar(pointerWithSize str, char c);
int summate(pointerWithSize integers);

然后你会因为不得不在后一类函数中进行强制转换而烦恼。例如。你最终得到这样的东西: /* 这里面 countOccurancesOfChar */ 如果(((char *)str.m)[i] == c){ /* ..或者这个里面的总结:*/ sum += ((int * )integers.m)[i];

所以你会遇到很多函数专门在 "strings with size" 上运行的情况,在所有这些情况下,你不想过多地使用 void 指针。所以相反,在那些情况下你开始做这样的事情:

typedef struct
{
    size_t v_n;
    char * v;
}
stringWithSize;
/* ... */
size_t countOccurancesOfChar(stringWithSize str, char c);
int parseFormatting(stringWithSize str, struct someFormat_t foo);

太棒了,因为现在所有与字符串相关的代码都不需要用强制转换弄乱了。但是,现在我不能使用我出色的通用函数 combinePointersWithSize 以语法上干净的方式连接 stringWithSize 中包含的字符串,就像我仍然根据每个指针的两个单独参数编写函数一样 -和尺寸对。

完成插图:

pointerWithSize combinePointersWithSize(pointerWithSize p1, pointerWithSize p2);
void * combineAlternative(void * p1, size_t p_n1, void * p2);
/* ... */
stringWithSize a, b, c;
/* ... */
/* This doesn't work, incompatible types: */
c = combinePointersWithSize(a, b);
/* But this works, because char * can be passed into void * parameter. */
c.v_n = a.v_n + b.v_n;
c.v = combineAlternative(a.v, a.v_n, b.v, b.v_n); /* Works fine. */

我考虑过的可能解决方案:

1:不要用这些结构作为参数编写我的函数,而是用单独的对参数编写它们。但这是我首先要避免的很大一部分 - 我喜欢 'cleanness' 和将 size_t 和指针捆绑在一个结构中所代表的意图的清晰度。

2:做这样的事情:

stringWithSize a, b, c;
/* ... */
pointerWithSize d;
d = combinePointersWithSize((pointerWithSize){.v=a.v, .v_n=a.v_n}, (pointerWithSize){.v=b.v, .v_n=b.v_n})
/* and then do either this: */
c.v = d.v;
c.v_n = d.v_n;
foo(c);
/* ..or this: */
foo((stringWithSize){.v=d.v, .v_n=d.v_n});

..但我认为大多数人会同意,这也与库函数中的转换的原始问题一样糟糕或更糟。从表面上看,它看起来更糟,因为它将转换负担卸载到客户端代码而不是库代码,后者有望在 implemented/completed(包括 testing/etc)之后相当稳定。另一方面,如果您确实保留了根据包含 pointerWithSize 的 void * 定义的每个函数,您最终可能会强制将类似的转换强制转换为您在自己的函数内部、它们代码的其他地方所做的那种类型转换,更糟糕的是,您重新失去了编译器对你大喊大叫的优势,因为现在代码在同一个 pointerWithSize 结构中承载所有内容。

我还担心有多少编译器能够优化此解决方案的两个变体中的第一个变体(其中 'd' 服务器仅作为临时结果持有者。

3:指针联合。而不是我之前的 pointerWithSize 示例,我会这样做:

typedef union
{
    void * void;
    char * char;
    int * int;
    /* ...and so on... */
}
rainbowPointer;

typedef struct
{
    size_t v_n;
    rainbowPointer v;
}
pointerWithSize;

乍一看这已经足够好了。但是,我经常最终想要存储某些结构的数组,这些结构特定于我在这个 "pointer with size" 构造中处理的程序,在这种情况下,预定义的指针类型联合对我来说毫无用处,我仍然会马上回来解决这个问题。

4:我可以为每个置换指针类型编写包装函数。我什至可以编写类似函数的宏来定义这些具有大小的指针结构类型中的每一个,这将同时生成包装函数。例如:

#define pointerWithSizeDef(T, name) \
typedef struct \
{ \
    size_t v_n; \
    T * v;
} \
name; \
foo_ ## name (name p1) \
{ \
    /* generic function code defined in macro */ \
    /* Or something like this: */ \
    foo((pointerWithSize){.v=p1.v, .v_n=p1.v_n});
};
/* Then, stuff like this: */
pointerWithSizeDef(char, stringWithSize)

我的直觉是迟早这种方法会变得笨拙。

5: 如果有一种机制对性能没有影响,但在其他方面没有吸引力,我可以将我的泛型函数写成类似函数的宏,然后调用底层的实际函数:

int foo_actual(void * v, size_t v_n);
#define foo(p) \
foo_actual(p.v, p.v_n);

..或者甚至是这样的东西,来替换转换语法:

#define castToPointerWithSize(p) \
((pointerWithSize){.v=p.v, .v_n=p.v_n})
/* ... */
stringWithSize a;
foo(castToPointerWithSize(a));

但是正如 possible-solution-#5 的这些示例所示,我实际上想不出一种不会很快成为可能问题的方法(例如,如果有人想放置一个函数调用在上面的例子中返回一个 pointerWithSize 来代替 'p' - 你会 运行 调用函数两次,从代码中看出来一点也不明显。

所以我认为我想到的任何解决方案都不足以满足我的用例,所以我希望你们中的一些人知道我可以在这里利用的一些 C 语法或机制很容易在两个结构之间转换/"cast",除了其中一个成员的指针类型外,它们完全相同。

您应该能够将一个转换为另一个,方法是将一个转换为指针,将其转换为另一种类型的指针,然后取消引用。这也将反向工作。

struct charPtrWithLen
{
 size_t len;
 char * charPtr;
};

struct voidPtrWithLen
{
 size_t len;
 void * voidPtr;
};

int main() {
    struct charPtrWithLen cpwl = {.len = 6, .charPtr = "Hello"};

    struct voidPtrWithLen vpwl = *(struct voidPtrWithLen *)&cpwl;

    return 0;
}

请注意,这仅在两个结构的结构布局相同时才有效。

首先,根据标准的字母,任何类型的 "actual" 转换都是不允许的,因为 C 所有指针都具有相同的格式。从某种任意指针类型到 void 指针的转换允许涉及表示的转换(当您将其转换回以访问数据时会反转),包括可能到不同大小的指针或存在于单独的地址 space。所以简单地重新解释位模式来改变指针类型是不安全的; void* 的位模式不保证有任何特定的含义,并且不保证其他类型的位模式以任何特定方式相关。 (有多少系统真正利用了这一点,我不知道。)

由于 void* 和其他类型之间的显式转换必须存在于某处,因此使用整值转换可能是最安全的想法。您可以做的是定义一个宏来快速轻松地为您生成 "cast functions",例如:

#define GEN_CAST(NAME, FROM_TYPE, TO_TYPE)     \
  static inline TO_TYPE NAME(FROM_TYPE from) { \
    return (TO_TYPE){ .v=p.v, .v_n=p.v_n };    \
  }

GEN_CAST(s_to_v, stringWithSize, pointerWithSize)
GEN_CAST(v_to_s, pointerWithSize, stringWithSize)

...然后您可以使用它来代替表达式中的强制转换运算符:

stringWithSize a, b, c;
pointerWithSize d;
d = combinePointersWithSize(s_to_v(a), s_to_v(b));
foo(v_to_s(d));

一个好的编译器应该认识到在通用平台上转换函数是一个身份操作,并完全删除它。