【问题标题】:Problem using reinterpret_cast<> in c++在 C++ 中使用 reinterpret_cast<> 的问题
【发布时间】:2010-12-24 20:34:29
【问题描述】:

我正在尝试将数据流转换为结构,因为数据流由固定宽度的消息组成,并且每条消息也具有完整定义的固定宽度字段。我计划创建一个结构,然后使用 reinterpret_cast 将指向数据流的指针转换为结构以获取字段。我做了一些测试代码并得到了奇怪的结果。任何人都可以解释为什么我得到这些或如何更正代码。 (数据流将是二进制和字母数字混合的,但我只是用字符串测试)

#pragma pack(push,1)
struct Header 
{
    char msgType[1];
    char filler[1];
    char third[1];
    char fourth[1];
};
#pragma pack(pop)

int main(void)
{
    cout << sizeof(Header) << endl;

    char* data = "four";
    Header* header = reinterpret_cast<Header*>(data);
    cout << header->msgType << endl;
    cout << header ->filler << endl;
    cout << header->third << endl;
    cout << header->fourth << endl;
    return 0;
}

即将出现的结果是

4
four
our
ur
r

我认为四个,our 和 ur 正在打印,因为它找不到空终止符。如何解决空终止符问题?

【问题讨论】:

  • 请注意编译器可以在结构的字段之间添加空格。这意味着 memcpy 和 fread 可能无法按预期工作。最安全的过程是将数据分别复制到成员中。使用这种技术,您可以将额外的字符添加到空终止符的字符数组中,或者更好的是,将字符数组替换为 std::string。

标签: c++ pragma reinterpret-cast


【解决方案1】:

为了能够打印字符数组,并能够将其与以空字符结尾的字符串区分开来,您需要其他 operator&lt;&lt; 定义:

template< size_t N >
std::ostream& operator<<( std::ostream& out, char (&array)[N] ) {
     for( size_t i = 0; i != N; ++i ) out << array[i];
     return out;
}

【讨论】:

  • 这很有趣。如果一些字符串是空终止的,而有些则不是,因为长度为 8 的字段可能只有 1 个字符并且其余的空值来填充它,但甚至可能包含所有 8 个字符而没有空终止。这将在两种情况下打印 8 个字符。如果已满,如何打印 8,如果 null 终止,如何打印小于 8?
  • 你可以在循环体中添加if (array[i]==0 ) break;
  • 我将循环语句更改为 for( size_t i = 0; (i != N && array[i] != '\0'); ++i ) out
【解决方案2】:

你说的没有空终止符是对的。它再次打印“ur”的原因是因为您重复了 header->third 而不是 header->fourth。为什么不将这些变量声明为“char”,而不是“char[1]”?

struct Header 
{
    char msgType;
    char filler;
    char third;
    char fourth;
};

【讨论】:

  • 我有大约 20 条不同的消息,它们总共有 300 个或更多字段,所以我使用脚本为我生成结构,脚本将 1 放在那里,因为有些字段需要只是2个字符宽等等..
【解决方案3】:

问题不在于 reinterpret_cast(尽管使用它是一个非常糟糕的主意),而在于结构中事物的类型。它们应该是“char”类型,而不是“char[1]”类型。

【讨论】:

  • char 有效。如果我希望第一个字段的长度为 2 个字符,而其余的只有 1 个字符,那该怎么办?
  • 如果您仍然想在不包含空终止符的字符串上使用 reinterpret_cast,那么您就完蛋了。如果它可以包含空终止符,则将第一个字符设为 char[3] 和字符串“fo\0ur”或类似的东西。
  • 对 reinterpret_cast 的更好替代方案有何建议?
  • 在不了解您的问题的情况下很难说。但是为您的结构提供显式解析输入字符串的构造函数将是一个开始。
【解决方案4】:
#pragma pack(push,1)
template<int N>
struct THeader 
{
    char msgType[1+N];
    char filler[1+N];
    char third[1+N];
    char fourth[1+N];
};
typedef THeader<0> Header0;
typedef THeader<1> Header1;  
Header1 Convert(const Header0 & h0) {
   Header1  h1 = {0};
   std::copy(h0.msgType, h0.msgType + sizeof(h0.msgType)/sizeof(h0.msgType[0]), h1.msgType);
   std::copy(h0.filler, h0.filler+ sizeof(h0.filler)/sizeof(h0.filler[0]), h1.filler);
   std::copy(h0.third , h0.third + sizeof(h0.third) /sizeof(h0.third [0]), h1.third);
   std::copy(h0.fourth, h0.fourth+ sizeof(h0.fourth)/sizeof(h0.fourth[0]), h1.fourth);
   return h1;
}
#pragma pack(pop)


int main(void)
{
  cout << sizeof(Header) << endl;
  char* data = "four";
  Header0* header0 = reinterpret_cast<Header*>(data);
  Header1 header = Convert(*header0);
  cout << header.msgType << endl;
  cout << header.filler << endl;
  cout << header.third << endl;
  cout << header.fourth << endl;
  return 0;
}

【讨论】:

    【解决方案5】:

    根据我的经验,使用#pragma pack 会引起一些麻烦——部分原因是编译器没有正确弹出,也因为开发人员忘记弹出一个标题。像这样的一个错误和结构最终定义不同,具体取决于编译单元中包含的 order 标头。这是调试的噩梦。

    出于这个原因,我尽量不进行内存覆盖——您不能相信您的结构与您期望的数据正确对齐。相反,我创建了包含来自“本机”C++ 格式的消息数据的结构(或类)。例如,如果“填充”字段仅用于对齐目的,则不需要定义它。也许字段类型为intchar[4] 更有意义。尽快将数据流转换为“本机”类型。

    【讨论】:

    • 填充符是在我正在使用的消息协议中定义的。
    • 我理解在字节流表示中,有一个填充字节。但我敢打赌,在您的应用程序代码中,您永远不需要设置填充字节或检查它的值。这只是填充。如果您手动将流中的字节提取到“本机”结构中,则无需复制填充字节,您可以在流中跳过它。这就是我想说的。
    【解决方案6】:

    假设您想继续使用可覆盖的结构(这是明智的,因为它避免了 Alexey 代码中的副本),您可以将原始 char 数组替换为如下所示的包装器:

    template <int N> struct FixedStr {
        char v[N];
    };
    
    template <int N>
    std::ostream& operator<<( std::ostream& out, FixedStr const &str) {
        char const *nul = (char const *)memchr(str.v, 0, N);
        int n = (nul == NULL) ? N : nul-str.v;
        out.write(str.v, n);
        return out;
    }
    

    然后您生成的结构将如下所示:

    struct Header 
    {
        FixedStr<1> msgType;
        FixedStr<1> filler;
        FixedStr<1> third;
        FixedStr<40> forty;
    };
    

    您现有的代码应该可以正常工作。

    注意。您可以根据需要向 FixedStr 添加方法(例如,std::string FixedStr::toString()),只是不要添加虚拟方法或继承,它会很好地覆盖。

    【讨论】:

    • 这可以工作,但我需要具有 1 - 40 不同大小元素的结构,因此模板无法在那里工作。
    • FixedStr 到 FixedStr,你的意思是?您只需将字段宽度从代码中的方括号移动到我的尖括号中。
    • ...还添加了对空填充字段的支持。
    猜你喜欢
    • 2021-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 2011-06-10
    • 1970-01-01
    相关资源
    最近更新 更多