C++ C++;编写恐怖代码的例子还是绝妙的想法?
在以前的一位雇主,我们写的是二进制信息,必须“通过电线”传送到其他计算机。每封邮件都有一个标准标题,类似于:C++ C++;编写恐怖代码的例子还是绝妙的想法?,c++,optimization,syntax,C++,Optimization,Syntax,在以前的一位雇主,我们写的是二进制信息,必须“通过电线”传送到其他计算机。每封邮件都有一个标准标题,类似于: class Header { int type; int payloadLength; }; char* Header::GetPayload() { return ((char*) &payloadLength) + sizeof(payloadLength); } 所有数据都是连续的(头,后面紧跟着数据)。我们想要得到有效载荷,因为我们有一个指向头
class Header
{
int type;
int payloadLength;
};
char* Header::GetPayload()
{
return ((char*) &payloadLength) + sizeof(payloadLength);
}
所有数据都是连续的(头,后面紧跟着数据)。我们想要得到有效载荷,因为我们有一个指向头的指针。传统上,你可能会这样说:
class Header
{
int type;
int payloadLength;
};
char* Header::GetPayload()
{
return ((char*) &payloadLength) + sizeof(payloadLength);
}
甚至:
char* Header::GetPayload()
{
return ((char*) this) + sizeof(Header);
}
这似乎有点冗长,所以我想到:
char* Header::GetPayload()
{
return (char*) &this[1];
}
一开始它看起来相当令人不安,可能太奇怪了以至于无法使用——但非常紧凑。
关于它是辉煌的还是令人憎恶的,有很多争论
那么,哪一个是反编码犯罪,还是好的解决方案?你有过类似的权衡吗
-更新:
我们确实尝试过零大小的数组,但当时编译器发出了警告。
我们最终使用了内置技术:消息源于头。
它在实践中效果很好,但在原则上,你说的是一个消息头,这似乎有点尴尬。我个人认为,如果有犯罪,它是在向消息头索要有效负载 但是,只要你打算这样做,“这个+1”是一个很好的方法 理由:“&this[1]”是一段通用代码,它不需要您通过挖掘类定义来完全理解,也不需要在有人更改类的名称或内容时进行修复 顺便说一句,第一个例子是真正的反人类罪。在类的末尾添加一个成员,它将失败。在课堂上移动成员,它就会失败。如果编译器填充该类,它将失败 此外,如果您要假设编译器的类/结构布局与您的数据包布局匹配,那么您应该了解所讨论的编译器是如何工作的。例如,在MSVC上,您可能想了解
#pragma pack
有多少人认为“这个+1”或“&这(1)”很难阅读或理解。有点吓人。别误会我的意思,这很聪明,但你节省了你自己一个完整的加法操作,代价是使代码更难理解和阅读。我认为这种折衷是不值得的。我宁愿反对编码犯罪 这两种方法将生成完全相同的目标代码。第一,明确其意图。第二个是非常混乱的,唯一的优点是它节省了几个按键。(只要学学着打怪字就行了) 另外,请注意,这两种方法都不能保证有效。sizeof()对象包含用于单词对齐的填充,因此如果标题为:
class Header
{
int type;
int payloadLength;
char status;
};
您描述的两种方法的有效负载都从Header+12开始,而实际上很可能从Header+9开始。也许您应该使用详细的方法,但用#define宏替换它?通过这种方式,您可以在键入时使用速记,但任何需要调试代码的人都可以毫无疑问地进行操作。就我而言,他们基本上是一样的。两者都是字节杂耍的形式,这总是有风险的,但并非不可能正确。 第一种形式更易于接受和识别。 我个人会写:
char* Header::GetPayload()
{
return ((char*) this) + sizeof(*this);
}
我认为如果标头需要“返回”未包含在其中的数据,那么从一开始就存在缺陷 既然你已经把自己放在了这些下流的理由上,我真的很喜欢你的想法 但请注意,这不是一场选美比赛。你应该找到一个完全不同的解决方案。对于您介绍的GetPayload()的所有三个版本,如果没有您的进一步解释,我将无法理解到底发生了什么。如果它一直有效,那么它是一个优雅的解决方案 它通常在内存中工作,因为编译器将处理对齐问题,并且您可以假设负载在正确对齐的内存空间中跟随标头 我可以看到,当标题/有效负载对象“在线路上”流式传输时,这种情况就会出现,因为您使用的流式传输机制可能不关心在任何特定边界上对齐对象。因此,有效载荷可以直接跟随收割台,而无需填充,以强制其进行特定对齐
用一句话来说,优雅就是优雅。因此,只要您小心地流式处理它,它就是优雅的。您依靠编译器以特定的方式布局类。我会将消息定义为一个结构(由我定义布局),并拥有一个封装消息并提供其接口的类。清除代码=良好代码。“可爱”代码=错误(难以维护)代码
自从我写过C++以来,已经有一段时间了,所以请原谅任何语法问题。只是想表达一下大意。别忘了VC++可能会在类的
sizeof()
值上加上填充。由于提供的示例预期为8个字节,因此它是自动DWORD对齐的,所以应该是ok。检查
尽管如此,我同意,所提供的示例是某种程度的编码恐惧。许多Win32数据结构在跟随可变长度数据时在标头结构中包含指针占位符。一旦数据加载到内存中,这可能是引用该数据的最简单方法。MAPI结构就是这种方法的一个例子。首先,在“反编码犯罪”和“不错的解决方案”之间有很大的距离,但我认为这更接近前者 收割台是有效载荷的保持器吗 这是这里的基本问题——头和有效负载都应该由另一个保存整个消息的对象管理,这是请求有效负载的正确位置。它将能够做到这一点,而无需指针算法或索引 有鉴于此,我倾向于第二种解决方案,因为它更清楚正在发生的事情 但我们现在的处境似乎表明
struct bla {
int i;
int j;
char data[0];
}
struct header
{
short id;
short size;
}
struct foo
{
header hd;
short hit_points;
}
short get_foo_data(char *packet)
{
return reinterpret_cast<foo*>(packet)->hit_points;
}
void handle_packet(char *packet)
{
header *hd = reinterpret_cast<header*>(packet);
switch(hd->id)
{
case FOO_PACKET_ID:
short val = get_foo_data(packet);
//snip
}
}
class Header
{
int type;
int payloadLength;
char payload[0];
};
char* Header::GetPayload()
{
return payload;
}
#include <stdint.h>
#include <arpa/inet.h>
class Header {
private:
uint32_t type;
uint32_t payloadlength;
public:
uint32_t getType() { return ntohl(type); }
uint32_t getPayloadLength() { return ntohl(payloadlength); }
};
class Message {
private:
Header head;
char payload[1]; /* or maybe std::vector<char>: see below */
public:
uint32_t getType() { return head.getType(); }
uint32_t getPayloadLength() { return head.getPayloadLength(); }
const char *getPayload() { return payload; }
};