在标准C中,typedef 一样的结构体取两个不同的别名,编译会报错吗?怎么解决?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在标准C中,typedef 一样的结构体取两个不同的别名,编译会报错吗?怎么解决?相关的知识,希望对你有一定的参考价值。

例子如下
typedef struct
int idx;
ushort devid;
ushort reg;
ushort value;
tFixUnit;

typedef struct
int idx;
ushort devid;
ushort reg;
ushort value;
tYkUnit;

不会报错。typedef就是类型别名。一个类型有多少个别名都可以。
还有,你举的这个例子,说是相同的结构体,其实不是。
你这两个结构体都会被编译器生成两个完全不同的匿名结构体,然后分别被别名。
typedef struct Unit
int idx;
ushort devid;
ushort reg;
ushort value;
;

typedef struct Unit tFixUnit;
typedef struct Unit tYkUnit;
这才是同一个结构体类型两个别名。
参考技术A 为什么要取两个不同的名字呢?
木有什么意义啊。。。
参考技术B 应该不会报错的

如何从 C++ 处理 C 库中不同大小的类型

【中文标题】如何从 C++ 处理 C 库中不同大小的类型【英文标题】:How to handle differently sized type in C library from C++ 【发布时间】:2018-08-24 20:42:27 【问题描述】:

我有一个从 C++ 链接到的外部 C 库。 该库定义了一个使用 bool 的结构,并为 C 包含了一个 typedef。 问题是这个 typedef 使用 int,所以(在我的平台上)这个 bool 的大小不是 1 字节,而是 4 字节。

当我 #include 标头并在 C++ 中创建结构时,bool(作为 C++ 标准类型)的大小为 1 个字节,因此该结构具有完全不同的内存布局,即当我将它传递给 C 库以便在那里修改它时,会导致各种堆栈损坏。

有没有办法在不修改外部库的情况下解决这种情况?我想用 C++ 编译器而不是 C 编译器编译外部库会起作用,对吗?这是我解决这个问题的唯一机会吗?

lib.h:

#ifndef __cplusplus
typedef int bool;
#endif

typedef struct 
  int numValues;
  bool values[20];
 bArray;

int arraySize();

lib.c:

#include "lib.h"

int arraySize() 
    return sizeof(bArray);

main.cpp:

#include <iostream>

extern "C" 
#include "lib.h"


int main() 
    std::cout << "size in c++: " << sizeof(bArray) << std::endl
              << "size in c  : " << arraySize() << std::endl;
    return 0;

输出:

size in c++: 24
size in c  : 84

【问题讨论】:

在 C 代码中丢失 bool 类型定义。这是一个 C++ 关键字,所以即使尝试也是个坏主意。 @πάνταῥεῖ 我认为这个问题与对齐无关。 用 C++ 编译器编译 C 库不太可能工作,除非互操作性是库的设计目标。我认为您可能需要将 C 数据变为不透明类型并使用与 C++ 兼容的函数来访问数据。 最好的解决方案是不要在 C 代码中使用bool。人们使用BOOL 比。现在,如果这不能改变,我看到的唯一解决方案是一个可怕的 hack - 将库包含在 C++ 代码中,#define bool int,而不是在包含之后 #undefine bool 如何将lib.h 更改为使用int values[20]; [不使用typedef 或使用bool]。这与 lib 兼容(即 lib 本身不需要更改)。换句话说,与 lib 接口的所有 c++ 代码都应该看到 int 定义,您只需修改 lib.h 而不是任何实际库 【参考方案1】:

您可以编写自己的头文件并使用它来代替库提供的损坏的头文件:

fixed_lib.h

typedef struct 
  int numValues;
  int values[20];
 bArray;

int arraySize();

当然,这意味着您需要维护它以防库更新。但是,如果该库得到积极维护,那么更好的方法可能是发送拉取请求以解决上游问题。

此外,如果 C 库中还有其他依赖于 lib.h 的头文件,那么 lib.h 应该确实有一个头文件保护,它也必须在您的替换中使用,并且您必须注意包含首先替换。

如果没有标头保护,那么您必须复制所有您依赖的此类标头,如果这些标头也依赖于 lib.h。

我猜想用 C++ 编译器而不是 C 编译器来编译外部库是可行的,对吗?

只有当库恰好是用 C 的子集编写的,它也是有效的 C++ 时,它才有可能工作。此外,这个 C++ 编译版本不会与库的 C 版本二进制兼容,因此您不能拥有其他依赖于这个(最初)C 库的依赖项。


如果修改 C 库没问题,另一种方法是修复库头本身以使用实际的布尔数据类型:

lib.h

#ifndef __cplusplus
#include <stdbool.h>
#endif

typedef struct 
  int numValues;
  bool values[20];
 bArray;

int arraySize();

当然,这需要您重新编译库,并且它不再与使用int 的原始版本二进制兼容。此外,这会使库需要 C99 标准,并且不能再在 C89 中工作。

【讨论】:

这没关系,但我强烈建议使用固定大小的标准类型(int8_t、int32_t 等)并在整个结构上使用打包属性。将此视为网络数据传输 - 因为您无法控制事务的另一端。 谢谢,这或多或少是我采取的路线。我创建了一个库的内部分支并在那里应用了更改。现在,如果库会发生变化,我可以轻松地合并上游,并且与其他使用该库的 C 库完全兼容。 虽然这仍然是在修改外部库(尽管只是它的包含文件而不需要重新编译),但我接受了它作为我的解决方案,因为它是开销和维护成本最少的一个,而不是在包含标题之前,就像 #define bool int 一样丑陋。 另外,感谢您指出仅使用 C++ 编译器编译库可能会起作用,但也会破坏二进制兼容性并且可能弊大于利。【参考方案2】:

在这种情况下,没有通用的方法可以直接从 C++ 向/从 C 代码传递数据,因为库中的 bool 类型与 C++ 中的类型不同。这意味着允许 C 代码直接访问 C++ 代码使用的数据结构是极其危险的,反之亦然(例如,在一种语言中所做的更改可能不会在另一种语言中产生预期的效果)。

一般的解决方案是引入一个头文件,它可以从 C 和 C++ 代码中使用,但不允许从 C++ 代码访问 C 代码,反之亦然。

这意味着标头只能包含在 C 和 C++ 中都可接受的声明。换句话说,由于 bool 在您的第三方库中的工作方式与在 C++ 中的工作方式不同,因此该标头中不应有任何引用 bool 的内容。

例如,一个名为 interface.h 的头文件

#ifndef __cplusplus
    extern "C" 
#endif

typedef struct

    int numValues;
    char values[20];
 interface_bArray;

int interface_arraySize();

int interface_somefunc(interface_bArray *v);

#ifndef __cplusplus
    
#endif

在编译为 C++ 时,上面将所有声明包装在 extern "C" 块中。如果编译为 C,则不会。假设 C 和 C++ 编译器互操作,这将允许在两种语言中使用类型和函数。请注意,确保此标头不是 #include "lib.h" 很重要,因此 C++ 代码永远不会看到该标头。此标头上的包含保护可能是合适的(但与提供 C++ 和 C 代码之间的接口无关)。

我在声明中使用前缀 interface_ 以确保名称与 lib.h 中的名称不同。您可以选择您喜欢的任何前缀,只要它们不会在您的程序或 lib.h 中引入与其他人的名称冲突。

然后是一个 C 文件(比如 interface.c),它只会被编译为 C。

#include "interface.h"
#include "lib.h"

int interface_arraySize()

    return arraySize();


int interface_somefunc(interface_bArray *v)

     int i, retval;
     bArray temp;

     /*  copy data from a form understood in C++ code to form understood by C library */

     temp.numValues = v->numValues;
     for (i = 0; i < 20; ++i) temp.values[i] = !(!v->values[i]);  /* maps all non-zero to 1 */

     retval = some_function_from_lib(&temp);

     /*  copy data library back to a form understood in C++ code */

     v->numValues = temp.numValues;

     for (i = 0; i < 20; ++i) v->values[i] = !(!temp.values[i]);  /* maps all non-zero to 1 */

     return retval;

在这个 C 文件中,唯一看到的 bool 类型是库所理解的类型。

这意味着没有数据直接从 C++ 代码传递到库。取而代之的是制作一个副本,将其提供给库,然后将结果复制回来。

需要(间接)使用该库的 C++ 代码通过 #include "interface.h" 获得该库,并且从不包含 lib.h。

要构建您的程序,请照常编译库(可能使用 C 编译器),仅使用 C 编译器编译 interface.c,并照常编译其他代码(例如,酌情使用 C++ 或 C 编译器)。然后根据需要链接。

这种方法的一个优点是,如果库曾经更改(例如更新)版本,那么程序中唯一需要更新的文件是 interface.c 并且(仅当库引入新功能时您希望在 C++ 代码中利用)interface.h。

有两个缺点。

首先,当存在不兼容时(在这种情况下, C 和 C++ 代码之间的 bool 类型的不同概念)。如果您不愿意接受此类费用,则没有通用解决方案 - 任何解决方案都可能因 C 和 C++ 编译器、编译器版本等而异。

其次,如果库发生变化,您需要记住检查接口文件是否需要更新。这一成本的一部分是为同一事物拥有两种数据结构——一种仅在 C 代码中可见,另一种在 C++ 上,也可以传递给 C 代码并从那里复制。您可能会很幸运,发现如果库更改,interface.c 将无法编译。或者你可能不会。同样,我认为持续维护接口的成本是跨编程语言使用库的必要成本。这自然意味着库提供的函数必须具有足够的价值来证明这种开销是合理的,而不是滚动它的 C++ 特定版本。

【讨论】:

这可能是最干净的解决方案,因为它完全避免了重新编译库,甚至避免更改包含的头文件以匹配 C 布局。但是,它带来了巨大的成本,您已清楚地描述了这一点。我可能会选择更轻量级的解决方案。 我描述了成本。你假设这些成本是巨大的成本,但我实际上并没有这么建议。尽管成本不为零,但如果不进行分析, l 会打赌您高估了接口对程序的运行时影响。在没有证据的情况下进行优化(测试和分析),尤其是在可能损害程序正确性或可维护性的情况下,被描述为“过早的优化”是有原因的。 我更多的是指维护桥接接口而不是运行时成本,在我的情况下这完全不相关。【参考方案3】:

bool 是 C++ 中的关键字,大小为 1 字节。因此,当您包含 typedef 的这个头文件时,C 和 C++ 中的大小是不同的。所以数组大小会改变,二进制能力不会保持。 通过pragma push 更改构建标志不会改变任何内容。

只有两种方法可以解决这个问题(除了寻找/编写替代方案)。

快速的方法是编辑lib.h 头文件并将bool 的typedef 重命名并删除#ifndef __cplusplus。它应该可以在不重新编译库的情况下解决问题(如果您有源代码)。

其他解决方案是用其他 C 源代码包装库。创建wrapper.h,它将提供引入类型和引入函数声明。在wrapper.c 中包含wrapper.hlib.h 并实现到库函数的桥接以及包装类型和库类型之间的对话。在这种情况下,会有很多样板代码,但它肯定会工作。

如果您有库源,请将 typedef 更改为 typedef int bool; 并重新构建它。


我想出了比为bool 重命名 typedef 更好的解决方案。使用define 改变bool 的含义。基本上创建这种头文件:
// WrappedLib.h
#ifndef WRAPPED__LIB_H
#define WRAPPED__LIB_H

#ifndef __cplusplus
#error "This header file should be used only in C++ sources"
#endif

// this will fix size problem
#define bool int

extern "C" 
    #include <lib.h>


// restore normal behavior of bool
#undefine bool

#endif // WRAPPED__LIB_H

现在,由于在解释任何关键字之前处理了宏,因此无需对 lib 代码或标头进行任何修改,这将像魅力一样工作。

【讨论】:

bool 的大小不需要为 1 字节。 是的,但大多数编译器都使用这个大小。我现在不编译它的不同之处。 @MarekR 我不知道有这样的编译器。 bool 通常是字长,而不是 1 个字节 3 个领先的 C++ 编译器:clang、g++、VS。 @Marek - 在 VS 和(从内存中)g++ 中,sizeof(bool) 历来在编译器版本之间发生了变化,并且对编译(特别是优化)设置也很敏感。如果您可以安全地假设您的代码将仅在特定编译器上构建并且(风险更大)sizeof(bool) 永远不会随着编译器版本或编译设置而改变,那么您可能会假设sizeof(bool) 为 1。从长远来看,它编写不依赖于这种假设的代码会更安全——您根本无法控制编译器供应商。

以上是关于在标准C中,typedef 一样的结构体取两个不同的别名,编译会报错吗?怎么解决?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中typedef定义结构体指针的区别?

C语言结构体中struct和typedef struct有啥区别?

如何从 C++ 处理 C 库中不同大小的类型

C使用typedef或#define定义结构[关闭]

我在不同文件中使用结构时遇到问题

求解答c语言结构体定义中typedef的作用