我的项目中有许多类型的结构,另一个结构包含指向其中一个结构的指针。例如
struct one{int num = 1;};
struct two{int num = 2;};
struct three{int num = 3;};
// These structs hold many other values as well, but the first value is always `int num`.
我还有另一个结构,其中包含对这些结构的引用。我必须使用void*
,因为我不知道将要引用这些结构中的哪个。
struct Holder{void* any_struct};
我的问题是,我需要这些结构中的值,但是我有一个void
指针,我可以声明一个基本结构,即第一个变量是int
,将其强制转换并使用它从这些结构中提取num
变量,例如:
struct Base{int num};
((Base*) Holder->any_struct)->num
// Gives 1, 2 or 3
答案 0 :(得分:4)
如果您唯一需要提取的是num
,则可以使用memcpy
。
假设它始终为int
,始终优先且始终存在。
int num = 0;
memcpy(&num, Holder->any_struct, sizeof(int));
// Gives 1, 2 or 3 in num.
C99标准第6.7.2.1节的要点13:
经过适当转换的指向结构对象的指针指向其 初始成员。结构内可能存在未命名的填充 对象,但不是在开始时。
有关this answer中标准的更多信息。
答案 1 :(得分:3)
我认为这是可以接受的,并且我已经在其他C项目中看到了这种模式。例如,在libuv中。他们定义了一个类型uv_handle_t
并将其称为“基本句柄” ...这是他们页面中的信息(http://docs.libuv.org/en/v1.x/handle.html)
uv_handle_t是所有libuv句柄类型的基本类型。
结构已对齐,因此可以将任何libuv句柄转换为 uv_handle_t。这里定义的所有API函数都可以与任何句柄类型一起使用。
它们如何实现是您可以采用的模式。他们为公共字段定义了一个宏:
#define UV_HANDLE_FIELDS \
/* public */ \
void* data; \
/* read-only */ \
uv_loop_t* loop; \
uv_handle_type type; \
/* private */ \
uv_close_cb close_cb; \
void* handle_queue[2]; \
union { \
int fd; \
void* reserved[4]; \
} u; \
UV_HANDLE_PRIVATE_FIELDS \
/* The abstract base class of all handles. */
struct uv_handle_s {
UV_HANDLE_FIELDS
};
...然后他们使用此宏定义“派生”类型:
/*
* uv_stream_t is a subclass of uv_handle_t.
*
* uv_stream is an abstract class.
*
* uv_stream_t is the parent class of uv_tcp_t, uv_pipe_t and uv_tty_t.
*/
struct uv_stream_s {
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
};
此方法的优点是您可以通过更新此宏将字段添加到“基”类中,然后确保所有“派生”类都获得新字段。
答案 2 :(得分:2)
首先,C中不同结构类型之间类型转换的各种规则很复杂,除非有人知道使两个结构兼容的规则,严格的别名规则,对齐问题等等,否则不应该干预这些规则。
话虽如此,最简单的基类接口类似于您拥有的接口:
typedef struct
{
int num;
} base_t;
typedef struct
{
base_t base;
/* struct-specific stuff here */
} one_t;
one_t one = ...;
...
base_t* ref = (base_t*)&one;
ref->num = 0; // this is well-defined
在此代码中,base_t*
不是直接指向num
,而是指向结构中第一个对象为base_t
的对象。因此,最好取消引用。
但是,您的int num
遍及3个结构的原始代码不一定允许您从一种结构类型转换为另一种结构类型,即使您仅访问初始成员num
也是如此。关于严格的别名和兼容类型可能会引起问题的细节很多。
答案 3 :(得分:0)
您描述的使用指向“基本”结构的指针作为多个“派生”结构的别名的结构,尽管经常与struct sockaddr
之类的东西一起使用,但是{{3} }。
虽然有一些语言建议可以支持,特别是6.7.2.1p15:
在结构对象中,非位字段成员和 位域所在的单元的地址会增加 声明它们的顺序。 指向结构的指针 经过适当转换的对象指向其初始成员(或 如果该成员是位域,则指向它所在的单元 居住),反之亦然。内部可能有未命名的填充 一个结构对象,但不是在它的开始。
其他部分表明不是,特别是6.3.2.3讨论了允许的指针转换:
1 指向void的指针可以与任何对象类型的指针进行转换。指向任何对象类型的指针都可以转换为 指向void并再次返回的指针;结果应相等 指向原始指针。
2 对于任何限定词q,可以将指向非q限定类型的指针转换为指向该q限定类型的指针。 存储在原始指针和转换指针中的值应进行比较 相等。
3 一个值为0的整数常量表达式,或者将这种类型强制转换为void *的表达式,称为anull指针 不变。如果将空指针常量转换为指针类型, 结果指针(称为空指针)可以保证进行比较 不等于指向任何对象或函数的指针。
4 将空指针转换为另一种类型的指针将产生该类型的空指针,任何两个空指针应 比较相等。
5 整数可以转换为任何指针类型。除非先前指定,否则结果是实现定义的, 可能未正确对齐,可能未指向实体 类型,可能是陷阱表示。
6 任何指针类型都可以转换为整数类型。除非先前指定,否则结果是实现定义的。如果 结果不能以整数类型表示,行为 未定义。结果不必在值的范围内 任何整数类型。
7 指向对象类型的指针可能会转换为指向不同对象类型的指针。如果结果指针不正确 对于引用的类型对齐,则行为未定义。
否则,当再次转换回时,结果应进行比较 等于原始指针。当指向对象的指针是 转换为指向字符类型的指针,结果指向 对象的最低寻址字节。的连续增量 结果,根据对象的大小,产生指向其余对象的指针 对象的字节数。8 指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再次返回;结果应进行比较 等于原始指针。如果使用转换后的指针来调用 一个类型与所引用类型不兼容的函数 行为是不确定的。
根据以上内容,并不能说明允许从一个结构强制转换为另一个结构,其中第一个成员的类型相同。
但是,允许使用union
来做基本上相同的事情。 6.5.2.3p6节规定:
为了简化联合的使用,做出了一项特殊保证: 如果一个联合包含多个具有共同点的结构 初始序列(请参见下文),以及并集对象 当前包含这些结构之一,允许 在任何地方检查其中任何一个的共同初始部分 联合完成类型的声明可见。 对应的结构共享一个共同的初始序列 成员具有兼容的类型(对于位域,宽度相同) 对于一个或多个初始成员的序列。
所以您可以做的是定义一个包含所有可能的类型以及基本类型的联合:
union various {
struct base { int num; } b;
struct one { int num; int a; } s1;
struct two { int num; double b; } s2;
struct three { int num; char *c; } s3;
};
然后,您可以在需要使用这三个子类型的任何地方使用此联合,并且可以自由检查基本成员以确定类型。例如:
void foo(union various *u)
{
switch (u->b.num) {
case 1:
printf("s1.a=%d\n", u->s1.a);
break;
case 2:
printf("s2.b=%f\n", u->s2.b);
break;
case 1:
printf("s3.c=%s\n", u->s3.c);
break;
}
}
...
union various u;
u.s1.num = 1;
u.s1.a = 4;
foo(&u);
u.s2.num = 2;
u.s2.b = 2.5;
foo(&u);
u.s3.num = 3;
u.s3.c = "hello";
foo(&u);