【问题标题】:Are void* pointer and pointer to some structure (layout-) compatible?void* 指针和指向某些结构(布局)的指针是否兼容?
【发布时间】:2012-07-23 17:40:30
【问题描述】:

换句话说,我可以重新解释(不是转换!)void* 指针作为指向某种结构类型的指针(假设 void* 指针确实保存正确转换的有效结构地址)

其实我对以下场景很感兴趣:

typedef struct void_struct void_struct_t;

typedef somestruct
{ 
    int member;
    // ... other members ...
}somestruct_t;

union 
{
    void*          pv; 
    void_struct_t* pvs; 
    somestruct_t*  ps; 
}u;

somestruct_t s={};

u.pv= &s;

u.ps->member=1; // (Case 1)  Ok? unspecified? UB? 

u.pvs=(void_struct_t*)&s;

u.ps->member=1;  // (Case 2) )Ok?

我在 C11 标准中的发现对于案例 1 来说是相当令人失望的:

§6.2.5

28 指向 void 的指针应具有与 指向字符类型的指针。[脚注:相同的表示和对齐要求 意味着可互换性作为函数的参数,返回值来自 函数和联合成员。] 同样,指向合格或不合格的指针 兼容类型的版本应具有相同的表示和对齐方式 要求。所有指向结构类型的指针都应具有相同的表示和 对齐要求彼此。所有指向联合类型的指针都应具有相同的 表示和对齐要求。指向其他类型的指针不需要 具有相同的表示或对齐要求。

不过,案例 2 似乎是有效的,但我不能 100% 确定...

这个问题主要是面向 C 的,但我对 C++ 也很感兴趣(我希望代码在由 C++ 编译器编译时有效)。老实说,我在 C++11 标准中发现的甚至更少,所以即使案例 2 对我来说似乎也有问题……但是,我可能遗漏了一些东西。

[编辑] 这个问题背后的真正问题是什么?

我有一组(可能很大)定义为结构的类型。 对于每种类型,我需要定义一个伴随类型:

typedef struct companion_for_sometype
{
  sometype* p_object;
  // there are also other members
}companion_for_sometype;

显然,伴生类型将是 C++ 中的模板,但我需要 C 的解决方案 (更准确地说,对于“干净的 C”,即 C89 和 C++ 的交集,因为我希望我的代码也是有效的 C++ 代码)。

幸运的是,即使在 C 中也不是问题,因为我可以定义一个宏

DECLARE_COMPANION(type_name) typedef struct companion_for_##type_name
{
  type_name* p_object;
  // there are also other members
}companion_for_##type_name;

只需为需要伴侣的每种类型调用它。

还有一组对伴随类型的通用操作。 这些操作也由宏定义(因为在纯 C 中没有重载)。

其中一个操作,比如说

#define op(companion_type_object) blablabla

应该将void* 指针分配给伴随对象的p_object 字段, 即应该做这样的事情:

(companion_type_object).p_object= (type_name*) some_function_returning_pvoid(..)

但是宏不知道 type_name(只有一个伴生类型的 object 被传递给宏) 所以宏不能做适当的指针转换。

这个问题其实是受这个问题启发的。

为了解决这个问题,我决定将赋值中的目标指针重新解释为 void*,然后赋值给它。 可以通过用指针联合替换伴随声明中的指针来完成 (问题是关于这种情况),或者可以直接重新解释目标指针,例如:

*(void**) &(companion_type_object).p_object= some_function_returning_pvoid(..)

但如果不重新解释指针,我找不到任何解决方案(虽然我可能错过了一些可能性)

【问题讨论】:

  • 即使它在技术上是 UB,但实际上情况 1 很可能适用于任何编译器/系统,除非结构是多态的/使用继承。
  • @smerlin:我怀疑即使是指向多态结构的指针,它也很可能适用于大多数编译器,但是..
  • 是的,在大多数情况下,但它肯定不适用于使用虚拟多重继承的类/结构。
  • @user396672 - 你想做什么?也许我们可以回答如何做到这一点?
  • C 和 C++ 在这些方面确实存在差异,因此您的 C++ 标签可能具有误导性。

标签: c++ c


【解决方案1】:

void * 是一个可以保存任何对象指针类型的指针,包括所有指向结构类型的指针。因此,您可以将任何指向结构类型的指针分配给void *

void * 和指向结构类型的指针不能保证具有相同的表示,因此您的案例 1 是未定义的行为。

(C11, 6.2.5p28) "[...] 指向其他类型的指针不必相同 表示或对齐要求。”

【讨论】:

  • 您认为案例 2 有效吗?
  • struct void_struct 是什么?您没有在示例中声明结构类型。
  • 它只是 void 的替代品。我什至不想定义它,但如果需要它可以任意定义
  • void_struct_t* 只是一个使用 void* 指针解决可能的 UB 的工作。它实际上是“指向任何结构的指针”的占位符。我希望它与指向实际结构的指针的联合应该起作用......
  • Case 2 实际上同样未定义,因为它写入一个联合成员并从另一个成员读取。 C++标准中说“在一个联合中,在任何时候最多可以有一个非静态数据成员处于活动状态,即最多可以将一个非静态数据成员的值存储在一个随时联合。”。您只能从当前存储在联合中的成员中读取。大多数编译器都有一个允许这样做的扩展,但语言不允许。
【解决方案2】:

在 C 中,void * 自动转换为任何对象类型,所以这将起作用:

(companion_type_object).p_object = some_function_returning_pvoid(..)

在 C++ 中,您需要使用 static_cast,但您可以使用 decltype 找出所需的类型:

(companion_type_object).p_object = 
    static_cast<decltype(*(companion_type_object).p_object) *>(
        some_function_returning_pvoid(..))

在 C++03 中,您应该能够使用一些等效于 decltype 的编译器扩展。或者,您可以在companion_type_object 上提供一个宏生成的方法,将void * 转换为适当的类型:

static type_name *void_p_to_object_p(void *p) { return static_cast<type_name *>(p); }
...
(companion_type_object).p_object = companion_type_object.void_p_to_object_p(
    some_function_returning_pvoid(..))

【讨论】:

  • 但是 C 在从 void* (?) 进行强制转换时会生成警告
  • 不应该;你确定some_function_returning_pvoid实际上返回void *吗?
  • 谢谢你,现在我明白我真正应该做什么了。我需要一个特殊的宏,比如 null_ptr_assign(type_p,void_p) 并以不同的方式为 C++ 和 C 实现这个宏(对于一些具有 decltype 模拟的 C 编译器可能不同)
  • ... 和 null_ptr_assign(type_p,void_p) 可能是 C++ 案例的函数模板(不是宏)(没有 decltype,即使对 C++03 也有效)。我很遗憾我不能选择两个答案作为接受(ouah 的答案更符合原始问题)。我只能投票,虽然你的答案是我真正需要的。
  • @user396672 - 首先不要使用 void。 void 这个词是一个提示 - void 又名缺乏空间或体积。
猜你喜欢
  • 2016-03-21
  • 2014-10-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-15
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多