【问题标题】:using unions in function parameters在函数参数中使用联合
【发布时间】:2009-08-02 21:46:20
【问题描述】:

我最近发现有些东西可以编译(但不确定它是否合法)。我对这种东西的需求来自于:我的项目为选定的拱门输出机器代码。(可能与运行程序的拱门相同,也可能不同)。所以,我现在想支持最多 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,我无法弄清楚如何调用此函数。

【问题讨论】:

  • 我不认为在那种情况下联合算作一种类型。但就是这样:[earlz@EarlzBeta-~] $ gcc -v !从 /usr/lib/gcc-lib/amd64-unknown-openbsd4.5/3.3.5/specs 读取规范!配置:!螺纹型号:单! gcc 版本 3.3.5 (propolice) ! (!表示换行,因为 cmets 没有格式化。)
  • 你为什么不简单地用一个名字来声明联合呢?
  • 我想我可以,虽然我希望能够调用这个函数而不必声明一个新的联合变量等,那可能吗?
  • 好吧,你提出的代码不允许你这样做,所以你什么也没有失去。

标签: c function types parameters unions


【解决方案1】:

总结:是的,这在 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 draft 中,相关文字如下:

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

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

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


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

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

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

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), ....);

【讨论】:

  • +1 但是您正在创建一个命名联合实例,尽管是在函数中。我的理解(这很可能是错误的)是 OP 不想在参数列表(它将是无名的)之外的任何地方创建实例。而且我认为这是不可能的。
  • 是的,我想为他的方式提出一个“替代方案”:) 我认为他不喜欢它的主要原因是创建局部变量的样板代码。有了包装函数,就不再需要它了:)
  • 是的,这对我来说是一个可以接受的替代方案。我只是不想求助于大量的 (uint32_t) 或临时变量,而且为了方便用户调用功能。函数调用不会影响性能,因为它们足够简单,可以内联。
  • 如果编译器支持 C99,则可以通过使用复合文字来摆脱包装函数,例如 pcg_new_state(...., (union base_type){ .b32 = 4211 }, ....);
【解决方案2】:

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

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

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

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

【讨论】:

  • 像这样的整数类型不可能重载,而且我使用的是 C 而不是 C++,所以无论如何它都被抛到了窗外。
  • 我认为这是完全有效的 C。但我认为你不能传递参数(你将如何创建正确类型的联合?) - 除非你传递一个空指针并且参数是一个指针.
  • 想一想,这对我来说似乎是有效的 C 代码:TU 1:void f(union A { int a; char b; } c) { ... } 和 TU 2:union A { int a; char b; }; void f(union A c); int main(void) { union A a = { 1 }; f(a); } -两个联合都有兼容的类型。
  • 为什么不把它作为答案发布,这样我们就可以真正阅读它了?我不得不将它粘贴到 vim 中,然后对其进行编辑以弄清楚发生了什么。在一天结束时,您创建了一个命名联合的命名实例,OP(出于对我来说不透明的原因)似乎想要避免。
  • 您可以在 C 中的参数列表中声明类型吗?这真的很有趣……但确实让 Vim 的语法高亮变得困难。
【解决方案3】:

这个怎么样:

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;

}

【讨论】:

    【解决方案4】:

    您可以通过在函数中为联合提供名称来使其在 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);
    

    【讨论】:

      【解决方案5】:

      无论如何,工会的规模将是最大成员的规模 - 所以你不会得到任何东西。您不妨只设置参数 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 ... */
      }
      

      【讨论】:

      • 是的。但随后必须将所有内容强制转换为 uint**_t 或临时变量。如果你在函数中有很多代码,这两者都是丑陋的。而且你不认为说一个 16 位拱门不断地将 64 位整数转换为 16 位整数很昂贵吗?(以 8086 为例,它几乎不支持操作码集中的 32 位数字转换)
      • 从包含 uint64_t 的联合中拉出 uint16_t 对处理器来说并不比将 uint64_t 转换为 uint16_t 更有效——这就是我的观点。
      猜你喜欢
      • 1970-01-01
      • 2021-07-03
      • 2021-07-22
      • 2022-01-07
      • 2018-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多