我有一个结构,定义如下:
struct vector
{
(TYPE) *items;
size_t nitems;
};其中类型实际上可以是任何类型,而我有一个类似类型的不可知结构:
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:
指向空的指针应具有与指向字符类型的指针相同的表示和对齐要求。39)类似地,指向兼容类型的合格或非限定版本的指针应具有相同的表示和对齐要求。指向结构类型的所有指针应具有相同的表示和对齐要求。指向联合类型的所有指针应具有相同的表示和对齐要求。指向其他类型的指针不需要有相同的表示或对齐要求。
现在我的问题是,我应该担心这段代码可能会在某些体系结构上崩溃吗?
我可以通过重新排序我的结构来修复这个问题,例如,指针类型在末尾?:
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
发布于 2014-11-25 07:24:59
你的代码没有定义。
使用不兼容类型的lvalue访问对象会导致未定义的行为。
标准对此作了如下定义:
6.5 p7: 对象的存储值只能由具有下列类型之一的lvalue表达式访问: -与目标的有效类型相符的一种类型, -与对象的有效类型兼容的类型的限定版本, -与物体的有效类型对应的有符号或无符号类型的类型, -与该物体的有效类型的限定版本对应的有符号或无符号类型的类型, -在其成员中包括上述类型之一的聚合或联合类型(递归地包括小聚合或包含工会的成员),或 -性格类型。
struct向量和struct _vector_generic具有不兼容的类型,不适合于上述任何类别。在这种情况下,它们的内部表示与此无关。
例如:
struct vector v;
_vector_generic* g = &v;
g->size = 123 ; //undefined!同样的例子也是如此,您可以将struct向量的地址传递给函数,并将其解释为_vector_generic指针。
结构的大小和填充也可能不同,导致元素位于不同的偏移位置。
您可以做的是使用您的一般结构,如果取决于主代码中空指针的类型,则进行强制转换。
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*。
发布于 2015-04-27 14:02:13
问题中的代码调用未定义的行为(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.
}发布于 2014-11-25 08:07:24
为了便于讨论,让我们忽略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的标准地址宽度时,通常由非标准扩展(如far指针)处理。在所有这些情况下,指针都有不同的大小,您将使用上面的静态断言来检测这些情况。
https://stackoverflow.com/questions/27120486
复制相似问题