简体   繁体   English

C++ 将结构转换为 std::vector<char> 内存对齐

[英]C++ casting a struct to std::vector<char> memory alignment

I'm trying to cast a struct into a char vector.我正在尝试将结构转换为字符向量。 I wanna send my struct casted in std::vector throw a UDP socket and cast it back on the other side.我想发送我在 std::vector 中投射的结构,抛出一个 UDP 套接字并将其投射回另一侧。 Here is my struct whith the PACK attribute.这是我的带有 PACK 属性的结构。

#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
PACK(struct Inputs
{
    uint8_t structureHeader;
    int16_t x;
    int16_t y;
    Key inputs[8];
});

Here is test code:下面是测试代码:

auto const ptr = reinterpret_cast<char*>(&in);
std::vector<char> buffer(ptr, ptr + sizeof in);
//send and receive via udp
Inputs* my_struct = reinterpret_cast<Inputs*>(&buffer[0]);

The issue is: All works fine except my uint8_t or int8_t.问题是:除了我的 uint8_t 或 int8_t 之外,一切正常。 I don't know why but whenever and wherever I put a 1Bytes value in the struct, when I cast it back the value is not readable (but the others are) I tried to put only 16bits values and it works just fine even with the maximum values so all bits are ok.我不知道为什么,但是无论何时何地我在结构中放置 1Bytes 值,当我将其转换回该值时,该值不可读(但其他值是)我尝试只放置 16bits 值,即使使用最大值所以所有位都可以。

I think this is something with the alignment of the bytes in the memory but i can't figure out how to make it work.我认为这与内存中字节对齐有关,但我不知道如何使其工作。

Thank you.谢谢。

I'm trying to cast a struct into a char vector.我正在尝试将结构转换为字符向量。

You cannot cast an arbitrary object to a vector.您不能将任意对象强制转换为向量。 You can cast your object to an array of char and then copy that array into a vector (which is actually what your code is doing).您可以将对象转换为char数组,然后将该数组复制到向量中(这实际上是您的代码正在执行的操作)。

auto const ptr = reinterpret_cast<char*>(&in);
std::vector<char> buffer(ptr, ptr + sizeof in);

That second line defines a new vector and initializes it by copying the bytes that represent your object into it.第二行定义了一个新向量,并通过表示对象的字节复制到其中来初始化它。 This is reasonable, but it's distinct from what you said you were trying to do.这是合理的,但它与您所说的尝试做的事情不同。

I think this is something with the alignment of the bytes in the memory我认为这与内存中的字节对齐有关

This is good intuition.这是很好的直觉。 If you hadn't told the compiler to pack the struct, it would have inserted padding bytes to ensure each field starts at its natural alignment.如果您没有告诉编译器打包结构,它会插入填充字节以确保每个字段以其自然对齐开始。 The fact that the operation isn't reversible suggests that somehow the receiving end isn't packed exactly the same way.操作不可逆的事实表明接收端的打包方式不完全相同。 Are you sure the receiving program has exactly the same packing directive and struct layout?您确定接收程序具有完全相同的打包指令和结构布局吗?

On x86, you can get by with unaligned data, but you may pay a large performance cost whenever you access an unaligned member variable.在 x86 上,您可以使用未对齐的数据,但是每当您访问未对齐的成员变量时,您可能会付出很大的性能成本。 With the packing set to one, and the first field being odd-sized, you've guaranteed that the next fields will be unaligned.将打包设置为 1,并且第一个字段是奇数大小,您就可以保证接下来的字段将是未对齐的。 I'd urge you to reconsider this.我敦促你重新考虑这一点。 Design the struct so that all the fields fall at their natural alignment boundaries and that you don't need to adjust the packing.设计结构,使所有字段都落在其自然对齐边界上,并且您不需要调整包装。 This may make your struct a little bigger, but it will avoid all the alignment and performance problems.这可能会使您的结构体大一点,但它会避免所有对齐和性能问题。

If you want to omit the padding bytes in your wire format, you'll have to copy the relevant fields byte by byte into the wire format and then copy them back out on the receiving end.如果您想在您的有线格式中省略填充字节,您必须将相关字段逐字节复制到有线格式中,然后在接收端将它们复制回来。

An aside regarding:关于:

#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )

Identifiers that begin with underscore and a capital letter or with two underscores are reserved for "the implementation," so you probably shouldn't use __Declaration__ as the macro's parameter name.以下划线和大写字母或两个下划线开头的标识符是为“实现”保留的,因此您可能不应该使用__Declaration__作为宏的参数名称。 ("The implementation" refers to the compiler, the standard library, and any other runtime bits the compiler requires.) (“实现”指的是编译器、标准库和编译器需要的任何其他运行时位。)

1 1

vector class has dynamically allocated memory and uses pointers inside. vector 类具有动态分配的内存并在内部使用指针。 So you can't send the vector (but you can send the underlying array)所以你不能发送向量(但你可以发送底层数组)

2 2

SFML has a great class for doing this called sf::packet. SFML 有一个很好的类来做这个,叫做 sf::packet。 It's free, open source, and cross-platform.它是免费的、开源的、跨平台的。

I was recently working on a personal cross platform socket library for use in other personal projects and I eventually quit it for SFML.我最近正在开发一个用于其他个人项目的个人跨平台套接字库,最终我退出了 SFML。 There's just TOO much to test, I was spending all my time testing to make sure stuff worked and not getting any work done on the actual projects I wanted to do.要测试的东西太多了,我把所有的时间都花在了测试上,以确保工作正常,而没有在我想做的实际项目上完成任何工作。

3 3

memcpy is your best friend. memcpy 是你最好的朋友。 It is designed to be portable, and you can use that to your advantage.它旨在便携,您可以充分利用这一点。

You can use it to debug.你可以用它来调试。 memcpy the thing you want to see into a char array and check that it matches what you expect. memcpy 将您想要查看的内容放入 char 数组并检查它是否符合您的预期。

4 4

To save yourself from having to do tons of robustness testing, limit yourself to only chars, 32-bit integers, and 64-bit doubles.为了避免进行大量的健壮性测试,请将自己限制为仅使用字符、32 位整数和 64 位双精度数。 You're using different compilers?你使用不同的编译器? struct packing is compiler and architecture dependent. struct 打包依赖于编译器和体系结构。 If you have to use a packed struct, you need to guarantee that the packing is working as expected on all platforms you will be using, and that all platforms have the same endianness.如果您必须使用打包结构,则需要保证打包在您将使用的所有平台上按预期工作,并且所有平台都具有相同的字节序。 Obviously, that's what you're having trouble with and I'm sorry I can't help you more with that.显然,这就是您遇到的问题,很抱歉我无法为您提供更多帮助。 I would I would recommend regular serializing and would definitely avoid struct packing if I was trying to make portable sockets.如果我试图制作便携式套接字,我会推荐定期序列化并且肯定会避免结构打包。

If you can make those guarantees that I mentioned, sending is really easy on LINUX.如果你能做出我提到的那些保证,那么在 LINUX 上发送真的很容易。

// POSIX
void send(int fd, Inputs& input)
{
    int error = sendto(fd, &input, sizeof(input), ..., ..., ...);
    ...
}

winsock2 uses a char* instead of a void* :( winsock2 使用 char* 而不是 void* :(

void send(int fd, Inputs& input)
{
    char buf[sizeof(input)];
    memcpy(buf, &input, sizeof(input));
    int error = sendto(fd, buf, sizeof(input), ..., ..., ...);
    ...
}

Did you tried the most simple approach of:您是否尝试过以下最简单的方法:

unsigned char *pBuff = (unsigned char*)&in;
for (unsigned int i = 0; i < sizeof(Inputs); i++) {
    vecBuffer.push_back(*pBuff);
    pBuff++;
}

This would work for both, pack and non pack, since you will iterate the sizeof.这对打包和非打包都适用,因为您将迭代 sizeof。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM