在包含指向不同类型的指针的结构之间转换结构指针?

时间:2014-11-25 06:56:15

标签: c pointers struct casting size

我有一个结构,定义如下:

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

3 个答案:

答案 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*不同。
  • 由于对齐要求,每种结构类型都有不同的struct padding。

这两种似乎都不太可能,并且可以用静态断言躲避:

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)处理指针。在所有这些情况下,指针将具有不同的大小,您将使用上面的静态断言检测此类情况。