我有一个结构,定义如下:
struct vector
{
(TYPE) *items;
size_t nitems;
};
其中type可以是任何类型,我有类似的类型不可知结构:
struct _vector_generic
{
void *items;
size_t nitems;
};
第二个结构用于将任何类型的第一种结构传递给调整大小的函数,例如:
struct vector v;
vector_resize((_vector_generic*)&v, sizeof(*(v->items)), v->nitems + 1);
其中vector_resize
尝试realloc
内存中的向量中给定数量的项目。
int
vector_resize (struct _vector_generic *v, size_t item_size, size_t length)
{
void *new = realloc(v->items, item_size * length);
if (!new)
return -1;
v->items = new;
v->nitems = length;
return 0;
}
但是,C标准规定指向不同类型的指针不需要具有相同的大小。
6.2.5.27:
指向void的指针应具有相同的表示和对齐方式 要求作为指向字符类型的指针.39)同样,指针 合格或不合格版本的兼容类型应具备 相同的表示和对齐要求。所有指针 结构类型应具有相同的表示和对齐方式 彼此的要求。所有指向union类型的指针都应该有 相同的表示和对齐要求。指针 其他类型不需要具有相同的表示或对齐 要求。
现在我的问题是,我是否应该担心这些代码可能在某些架构上出现问题?
我可以通过重新排序我的结构来解决这个问题,使得指针类型在最后吗?例如:
struct vector
{
size_t nitems;
(TYPE) *items;
};
如果没有,我该怎么办?
有关我想要达到的目的的参考,请参阅:
https://github.com/andy-graprof/grapes/blob/master/grapes/vector.h
例如用法,见:
https://github.com/andy-graprof/grapes/blob/master/tests/grapes.tests/vector.exp
答案 0 :(得分:1)
您的代码未定义。
使用不兼容类型的左值访问对象会导致未定义的行为。
标准定义如下:
6.5 p7:
对象的存储值只能由具有其中一个的左值表达式访问 以下类型:
- 与对象的有效类型兼容的类型,
- 与对象的有效类型兼容的类型的限定版本
- 对应于有效类型的有符号或无符号类型 对象,
- 对应于合格版本的有符号或无符号类型的类型 有效的对象类型,
- 包含其中一种上述类型的聚合或联合类型 成员(包括,递归地,子集合或包含的联合的成员),或
- 字符类型。
struct vector和struct _vector_generic具有不兼容的类型,不适合上述任何类别。在这种情况下,他们的内部表示无关紧要。
例如:
struct vector v;
_vector_generic* g = &v;
g->size = 123 ; //undefined!
同样适用于将结构向量的地址传递给函数并将其解释为_vector_generic指针的示例。
结构的尺寸和填充也可能不同,导致元素定位在不同的偏移处。
你可以做的是使用你的通用结构,并根据void指针在主代码中保存的类型进行强制转换。
struct gen
{
void *items;
size_t nitems;
size_t nsize ;
};
struct gen* g = malloc( sizeof(*g) ) ;
g->nitems = 10 ;
g->nsize = sizeof( float ) ;
g->items = malloc( g->nsize * g->nitems ) ;
float* f = g->items ;
f[g->nitems-1] = 1.2345f ;
...
使用相同的结构定义,您可以为不同的类型分配:
struct gen* g = malloc( sizeof(*g) ) ;
g->nitems = 10 ;
g->nsize = sizeof( int ) ;
g->items = malloc( g->nsize * g->nitems ) ;
int* i = g->items ;
...
由于您正在存储类型的大小和元素的数量,因此很明显您的调整大小函数的样子(尝试它)。
您必须小心记住哪个类型在哪个变量中使用,因为编译器不会因为您使用void *而发出警告。
答案 1 :(得分:1)
您的问题中的代码调用未定义的行为(UB),因为您取消引用可能无效的指针。演员:
(_vector_generic*)&v
......由6.3.2.3第7段涵盖:
指向对象类型的指针可以转换为指向不同对象类型的指针。如果生成的指针未针对引用的类型正确对齐,则行为未定义。否则,当再次转换回来时,结果将与原始指针进行比较。
如果我们假设满足对齐要求,那么强制转换不会调用UB。但是,并不要求转换后的指针必须与原始指针“比较相等”(即指向同一个对象),甚至不要求它指向任何对象 - 也就是指针的值是未指定 - 因此,要取消引用此指针(不首先确定它等于原始指针)调用未定义的行为。
(许多知道C井的人发现这很奇怪。我认为这是因为他们知道指针转换通常编译为无操作 - 指针值只是保持原样 - 因此他们看到指针转换纯粹是类型转换但是,该标准并没有强制要求这样做。)
即使转换后的指针与原始指针的比较相等,6.5段7(所谓的“严格别名规则”)也不允许你取消引用它。实质上,您不能通过具有不同类型的两个指针访问同一个对象,但有一些有限的例外。
示例:
struct a { int n; };
struct b { int member; };
struct a a_object;
struct b * bp = (struct b *) &a_object; // bp takes an unspecified value
// Following would invoke UB, because bp may be an invalid pointer:
// int m = b->member;
// But what if we can ascertain that bp points at the original object?:
if (bp == &a_object) {
// The comparison in the line above actually violates constraints
// in 6.5.9p2, but it is accepted by many compilers.
int m = b->member; // UB if executed, due to 6.5p7.
}
答案 2 :(得分:0)
为了讨论的缘故,请忽略C标准正式说明这是未定义的行为。因为未定义的行为只是意味着某些东西超出了语言标准的范围:任何事情都可能发生,C标准不能保证。然而,可能有"外部"保证您使用的特定系统由制造系统的人制作。
在有硬件的现实世界中,确实有这样的保证。实践中只有两件事可能出错:
TYPE*
的表示形式或大小与void*
不同。这两种似乎都不太可能,并且可以用静态断言躲避:
static void ct_assert (void) // dummy function never linked or called by anyone
{
struct vector v1;
struct _vector_generic v2;
static_assert(sizeof(v1.items) == sizeof(v2.items),
"Err: unexpected pointer format.");
static_assert(sizeof(v1) == sizeof(v2),
"Err: unexpected padding.");
}
现在唯一可能出错的是,如果一个"指向x"具有相同的大小但不同的表示形式与"指向y"在您的特定系统上。我从未在现实世界的任何地方听说过这样的系统。但是,当然,没有任何保证:可能存在这种模糊,非正统的系统。在这种情况下,您是否想要支持它们,或者是否足以将其移植到世界上所有现有计算机的99.99%就足够了。
实际上,只有在系统上有超过一个指针格式的时候才能解决超出CPU标准地址宽度的内存问题,这通常由非标准扩展(如{{1)处理指针。在所有这些情况下,指针将具有不同的大小,您将使用上面的静态断言检测此类情况。