5

我最近发现有些东西可以编译(虽然不确定它是否合法)。我对这种东西的需求来自于此:我的项目为选定的拱门输出机器代码。(可能与运行程序的拱门相同,也可能不同)。所以,我现在想支持最多 64 位架构(同时也支持现有的 32 位和 16 位架构。)我目前的解决方案是 new_state 的“基础”只是一个 uint64_t,并根据需要手动转换为 16 位和 32 位. 不过,我发现您可以在函数参数中编译联合。所以编译的函数:

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz,
             union{
    uint16_t b16;
    uint32_t b32;
    uint64_t b64;
}base ,int self_running);

这种事情是否完全“合法”或受到任何其他编译器的支持?而且,我不知道如何在不创建联合然后将此联合传递给 new_state 的情况下调用此函数。

4

5 回答 5

7

总结一下:是的,这在 C 中是有效的,尽管在 C++ 中是非法的。后者包含解释差异的注释

更改:在 C++ 中,类型可能未在返回或参数类型中定义。在 C 中,这些类型定义是允许的

例子:

void f( struct S { int a; } arg ) {} // valid C, invalid C++
enum E { A, B, C } f() {} // valid C, invalid C++
  • 理由:在比较不同编译单元中的类型时,C++ 依赖于名称等价,而 C 依赖于结构等价。关于参数类型:由于参数列表中定义的类型将在函数的范围内,因此 C++ 中唯一合法的调用将来自函数本身。
  • 对原始特征的影响:删除语义上定义明确的特征。
  • 转换难度:语义转换。类型定义必须移动到文件范围或头文件中。
  • 使用范围:很少。这种类型定义的风格被视为糟糕的编码风格。

C 中的结构等价是通过“类型兼容性”的概念完成的。这允许 C 将许多类型视为相同,即使它们在理论上是不同的 - 因为它们是在两个不同的翻译单元中声明的。在 C++ 中,这个概念不存在,因为类型具有链接并且匹配到同一个实体(即允许成员函数相互链接)。

请注意,上面引用的解释基于 C89,在确定类型兼容性时没有考虑结构的标记名称。在C89 草案中,相关文本如下:

此外,如果在不同的翻译单元中声明的两个结构、联合或枚举类型具有相同的成员数量、相同的成员名称和兼容的成员类型,则它们是兼容的;对于两个结构,成员的顺序应相同;

在 C99 中,类型检查更加严格:如果一个结构具有标记名称,则另一个结构声明必须具有相同的标记名称。因此,在您的未命名联合类型的情况下,要在另一个具有兼容类型的 TU 中声明一个函数,如果您想拥有有效的 C99 代码(没有未定义的行为),您将再次需要一个未命名的联合 - 您不能“欺骗”周围,并且在一个 TU 中使用命名联合,在另一个 TU 中使用未命名联合。不过,在我看来,这个“技巧”对 C89 有效。C99 TC3 6.2.7/1

此外,如果它们的标记和成员满足以下要求,则在单独的翻译单元中声明的两种结构、联合或枚举类型是兼容的:如果用标记声明一个,则另一个应用相同的标记声明。如果两者都是完整类型,则适用以下附加要求:它们的成员之间应存在一对一的对应关系,使得每对对应的成员都声明为兼容的类型,并且如果对应对的一个成员是用一个名字声明,另一个成员用相同的名字声明。对于两个结构,相应的成员应以相同的顺序声明。


你想要的方式是行不通的。调用函数会将参数转换为参数的类型,就像通过正常赋值一样。

因此,要使其正常工作,您必须有一个与参数类型兼容的参数。对于在同一个翻译单元中声明的两个联合,这意味着它们的类型必须相等 - 这是您在同一个翻译单元中提出兼容类型的唯一方法。但这不起作用,因为未命名联合的声明创建了一个唯一的新类型 - 无法使用另一个声明“引用”它。

所以,总结一下——你必须给联合类型一个名字。为了避免创建一个单独的变量来传递所需的基本参数,我会在函数之外声明它,并创建返回一个你可能会传递的联合的函数

union base_type {
        uint16_t b16;
        uint32_t b32;
        uint64_t b64;
};

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz,
                  union base_type base,int self_running);

union base_type base_b16(uint16_t t) 
{ union base_type b; b.b16 = t; return b; }
union base_type base_b32(uint32_t t) 
{ union base_type b; b.b32 = t; return b; }
union base_type base_b64(uint64_t t) 
{ union base_type b; b.b64 = t; return b; }

现在,它可能如下所示

pcg_new_state(...., base_b32(4211), ....);
于 2009-08-02T23:11:07.067 回答
1

我快速浏览了(编辑:C++)标准,并没有在 8.3.5 - Functions [dcl.fct] 或 9.5 - Unions [class.union] 中看到任何禁止它的内容。作为一个半受过教育的猜测,我会说通过一个联合是合法的,但不能像这样声明一个内联。海合会给出:

错误:类型可能未在参数类型中定义。

所以你必须提前定义类型。

然而,即使它是合法的,也不意味着它是一个好主意。只是看着它,我建议也许重载可以提供更好的解决方案。但是当然你最了解你的代码......只是认为你可能想要寻找一个更简单、更惯用的解决方案。

于 2009-08-02T22:05:14.900 回答
0

这个怎么样:

typedef struct _base
{
    union
    {
        uint16_t b16;
        uint32_t b32;
        uint64_t b64;
    };
}Base;

int func(Base b)
{
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{

    Base b;
    b.b16 = 0xFFFF;
    func(b);

    return 0;

}
于 2009-08-02T22:17:47.553 回答
0

您可以通过在函数中为联合提供名称来使其在 GCC 中编译,但由于定义的范围,您将无法使用它,正如 GCC 警告的那样:

test_union.c:14: warning: ‘union X’ declared inside parameter list
test_union.c:14: warning: its scope is only this definition or declaration, which is probably not what you want

代码:

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz,
                         union X{
        uint16_t b16;
        uint32_t b32;
        uint64_t b64;
}base ,int self_running);
于 2009-08-02T22:18:59.610 回答
0

无论如何,工会的规模将是最大成员的规模——所以你不会得到任何东西。您不妨只设置参数 uint64_t,因为从较大的无符号类型到较小的无符号类型的转换是明确定义的,并且通常实现起来很便宜。(例如,将 uint64_t 分配给 uint16_t 可以通过取更宽类型的最低 16 位来完成)。

编辑:例如

int pcg_new_state(pcg_state *s,int arch,void *mem,int sz, uint64_t param_base ,int self_running)
{
    /* Implementation for 16 bit arch */
    uint16_t base = param_base;

    /* ... more code ... */
}
于 2009-08-02T23:30:38.843 回答