简体   繁体   中英

Unaligned memory acces with array

In a C program, having an array that is meant to work as a buffer FooBuffer[] for storing the contents of a data member of a struct like this:

struct Foo {
    uint64_t data;
};

I was told that this line might cause unaligned access:

uint8_t FooBuffer[10] = {0U};

I have some knowledge that unaligned access depends on the alignment offset of the processor and in general, it consumes more read/write cycles. Under what circumstances would this cause unaligned memory access and how could I prevent it?

Edit: A variable of type struct Foo would be stored in the buffer. Particularly, its member data would be split up into eight bytes that would be stored in the array FooBuffer . See attached code with some options for this.

#include <stdio.h>
#include <string.h>

typedef unsigned long uint64;
typedef unsigned char uint8;

struct Foo
{
    uint64 data;
};

int main()
{
    struct Foo foo1 = {0x0123456789001122};
    uint8 FooBuffer[10] = {0U};
    
    FooBuffer[0] = (uint8)(foo1.data);
    FooBuffer[1] = (uint8)(foo1.data >> 8);
    FooBuffer[2] = (uint8)(foo1.data >> 16);
    FooBuffer[3] = (uint8)(foo1.data >> 24);
    FooBuffer[4] = (uint8)(foo1.data >> 32);
    FooBuffer[5] = (uint8)(foo1.data >> 40);
    FooBuffer[6] = (uint8)(foo1.data >> 48);
    FooBuffer[7] = (uint8)(foo1.data >> 56);
    
    struct Foo foo2 = {0x9876543210112233};
    uint8 FooBuffer2[10] = {0U};
    
    memcpy(FooBuffer2, &foo2, sizeof(foo2));

    return 0;
}

However, it is not clear how this process is done since a piece of privative software performs the operation. What would be the scenarios that could result in unaligned memory access after the "conversion"?

Defining either a structure such as struct Foo { uint64_t data; } struct Foo { uint64_t data; } or an array such as uint8_t FooBuffer[10]; and using them in normal ways will not cause an unaligned access. (Why did you use 10 for FooBuffer ? Only 8 bytes are needed for this example?)

A method that novices sometimes attempt that can cause unaligned accesses is attempting to reinterpret an array of bytes as a data structure. For example, consider:

// Get raw bytes from network or somewhere.
uint8_t FooBuffer[10];
CallRoutineToReadBytes(FooBuffer,...);

// Reinterpret bytes as original type.
struct Foo x = * (struct Foo *) FooBuffer; // Never do this!

The problem here is that struct Foo has some alignment requirement, but FooBuffer does not. So FooBuffer could be at any address, but the cast to struct Foo * attempts to force it to an address for a struct Foo . If the alignment is not correct, the behavior is not defined by the C standard. Even if the system allows it and the program “works,” it may be accessing a struct Foo at an improperly aligned address and suffering performance problems.

To avoid this, a proper way to reinterpret bytes is to copy them into a new object:

struct Foo x;
memcpy(&x, FooBuffer, sizeof x);

Often a compiler will recognize what is happening here and, especially if struct Foo is not large, implement the memcpy in an efficient way, perhaps as two load-four-byte instructions or one load-eight-byte instruction.

Something you can do to help that along is ask the compiler to align FooBuffer by declaring it with the _Alignas keyword:

uint8_t _Alignas(Struct Foo) FooBuffer[10];

Note that that might not help if you need to take bytes from the middle of a buffer, such as from a network message that includes preceding protocol bytes and other data. And, even if it does give the desired alignment, never use the * (struct Foo *) FooBuffer shown above. It has more problems than just alignment, one of which is that the C standard does not guarantee the behavior of reinterpreting data like this. (A supported way to do it in C is through unions, but memcpy is a fine solution.)

In the code you show, bytes are copied from foo1.data to FooBuffer using bit shifts. This also will not cause alignment problems; expressions that manipulate data like this work just fine. But there are two issues with it. One is that it nominally manipulates individual bytes one by one. That is perfectly legal in C, but it can be slow. A compiler might optimize it, and there might be built-ins or library functions to assist with it, depending on your platform.

The other issue is that it puts the bytes in an order according to their position values: The low-position-value bytes are put into the buffer first. In contrast, the memcpy method copies the bytes in the order they are stored in memory. Which method you want to use depends on the problem you are trying to solve. To store data on one system and read it back later on the same system, the memcpy method is fine. To send data between two systems using the same byte ordering, the memcpy method is fine. However, if you want to send data from one system on the Internet to another, and the two systems do not use the same byte order in memory, you need to agree on an order to use in the network packages. In this case, it is common to use the arrange-bytes-by-position-value method. Again, your platform may have builtins or library routines to assist with this. For example, the htonl and ntohl routines are BSD routines that take a normal 32-bit unsigned integer and return it with its bytes arranged for network order or vice-versa.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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