【问题标题】:How to update a C/C++ struct with compatibility如何更新具有兼容性的 C/C++ 结构
【发布时间】:2020-12-17 10:41:54
【问题描述】:

我在历史库中定义了一个struct,唉,不幸的类型选择:unsigned char * 而不是char*。

struct MyStruct {

  unsigned char * myMember;
};

这个struct 被大量的 C 应用程序使用,而且越来越多的 C++ 应用程序使用它。这些 C++ 应用程序在与 strlen 一起使用时会引发错误,例如,强制我们进行强制转换。很多演员表。

我想解决这个问题。

但有一点极其重要:必须绝对兼容,无需修改,对现有项目无影响。

我想过制作一个union。唉,我的变量不能有相同的名字。

struct MyStruct {

  union {
    unsigned char * myMember;
    // char * myMember; Obvioulsy, rejected by the compiler.
};

恐怕没有任何明显的解决方案。我错了吗?

我无法更改结构的大小,因为它映射到大小不可更改的共享内存中。

【问题讨论】:

  • 你试过 std::strlen() 吗??
  • 如果 char 未签名,您可能只需删除 unsigned 就可以逃脱
  • 如果您可以更改MyStruct,为什么不按照@largest_prime_is_463035818 的建议删除unsigned?
  • 当谈到兼容性时,最好知道你的结构是如何使用的。
  • 基本上您要求更改代码而不是更改代码。没有解决办法。如果您决定继续进行更改会更好,那么您必须接受存在一些风险。我的首选方法是将myMember 更改为其他名称以及类型的更改。这将迫使您检查名称的每一次使用,并进行适当的更改。

标签: c++ c casting compatibility


【解决方案1】:

注意:你不能在 C++ 中解决这个问题,因为严格的 C 也不喜欢 unsigned char*。即使使用宽松的 C 编译器设置,您也会得到(使用 gcc 默认设置的示例):

赋值中的指针目标符号不同 [-Wpointer-sign]|

即使做一些基本的事情,比如简单的赋值 ms.myMember="hello";。

这是一个 C11 解决方案。

struct MyStruct {
  union // anonymous union
  {
    unsigned char * myMember;
    char* myCharMember;
  };
};  

#define myMember myCharMember

测试代码:

struct MyStruct ms;  
_Generic(ms.myMember, 
         char*:           puts("I'm a char*"), 
         unsigned char*:  puts("I'm an unsigned char*"));

没有#define,它会告诉你"I'm an unsigned char*",但有#define,"I'm a char*"。

【讨论】:

  • 除非 C++ 功能失调的联合类型双关语规则搞砸了这个解决方案,否则它应该在 C 和 C++ 中都有效。否则,好吧,向 C++ 委员会提交关于类型双关规则的 DR……宏意味着实际的 myMember 从未实际使用过,因此应该避开联合类型双关。
  • 您是否建议仅定义需要签名的位置?那确实是 C++ 中的 UB,并且也会破坏 ABI。
  • @PasserBy 定义将与结构定义放在一起,可能在标题中。这样就不可能在没有宏的情况下使用结构。
  • 我一头雾水,那这与简单地改变类型有什么不同?
  • 我倾向于假设 OP 不简单地更改现有成员的数据类型的原因是它会导致其他类似的问题与不同的客户端代码,可能在一些相同的翻译单位。如果某些代码希望类型为 char * 而同一 TU 中的其他代码希望它为 unsigned char *,那么可能唯一可行的解​​决方案是更改代码以对数据类型具有一致的期望或将需要的数据。
【解决方案2】:

有一个更简单的补救措施。通常,当您因为不得不多次执行某件事而烦恼时,只需在函数中执行一次即可解决:

std::size_t unsigned_strlen(const unsigned char* str) noexcept
{
    return std::strlen(reinterpret_cast<const char*>(str));
}

【讨论】:

  • C 使用者不需要任何解决方案,因为由于语言允许的隐式转换,他们可以直接使用 strlen。
  • 不可能只为std::strlen() 提供一个覆盖const unsigned char* 参数并使用与给定函数相同的主体吗?还是不赞成重写标准库?
  • @AdrianMole 不允许向std 命名空间添加函数。
  • 好的 - 不知道(从未尝试过)。尽管您可以在没有显式 std:: 命名空间前缀的情况下提供覆盖?
  • 您必须为 all 字符串处理函数执行此操作,而不仅仅是 strlen。相当乏味。
【解决方案3】:

在不破坏现有项目的情况下,您无法将myMember 设为char* 或任何其他类型,原因很简单,可以推断出它的类型

void foo(decltype(MyStruct::myMember));

这将导致 ABI 问题,其中最轻微的是链接器错误。

一半是为演员表提供速记

struct MyStruct
{
#ifdef __cplusplus
   char* signed_myMember() { return (char*)myMember; }
#endif
};

幸运的是,char 可以在 C++ 中为任何东西起别名,这使得这变得合法。

【讨论】:

  • 此外,相同基础整数类型的有符号和无符号变体可以相互别名。
【解决方案4】:

由于您可以更改MyStruct,因此我建议您只删除unsigned,这会破坏现有的ABI,并需要使用该结构重新编译所有内容。

如果由于某种原因这不是一个选项,您可以继承原始结构并为 C++ 应用程序提供一个 getter 方法。

// use this in C apps
struct MyStruct {
    unsigned char* myMember;
};

// use this in C++ apps
struct MyStructCpp : MyStruct {
    MyStructCpp(const MyStruct& rhs) : MyStruct(rhs) {}
    char* myMemberAsChar() const { return reinterpret_cast<char*>(myMember); }    
};

【讨论】:

  • 可能也有助于使 MyStructCpp(隐式?)可从 MyStruct 构造。
  • @eerorika 确实!已添加!
  • @TedLyngmo 我在我的回答中添加了一个解释(在我的评论中粘贴了错误信息,抱歉)。问题是你需要让它在 C 和 C++ 中都编译干净。
  • 绝对不要"只删除未签名的",这会导致ABI问题。
  • @TedLyngmo 先决条件是兼容性,我至少要提一下这会导致 ABI 问题。
【解决方案5】:

鉴于您的目标是创建一个完美岛而不是修复世界,您可以从这个旧结构创建一个带有 implicit 构造函数的适配器,并在“完美岛”代码中使用该适配器。

struct BetterMyStruct {
  char* myMember;
  
  BetterMyStruct(MyStruct const& x): myMember((char*)x.myMember) {
    // any ugly secret hacks you want or need
  }

};

...

size_t better_strlen(BetterMyStruct const& x) {
  // perfect function
  return std::string_view(x.myMember).size();
}

...

better_strlen(MyStruct(...)); // perfect function invocation on imperfect inputs

【讨论】:

    猜你喜欢
    • 2015-03-06
    • 2020-07-31
    • 1970-01-01
    • 1970-01-01
    • 2011-02-23
    • 2012-10-29
    • 2013-11-26
    • 1970-01-01
    • 2011-06-09
    相关资源
    最近更新 更多