用于在 ARM 上进行未对齐内存访问的函数



我正在做一个从内存中读取数据的项目。其中一些数据是整数,并且在未对齐的地址访问它们时出现问题。我的想法是为此使用 memcpy,即

uint32_t readU32(const void* ptr)
{
uint32_t n;
memcpy(&n, ptr, sizeof(n));
return n;
}

我找到的项目源代码中的解决方案类似于以下代码:

uint32_t readU32(const uint32_t* ptr)
{
union {
uint32_t n;
char data[4];
} tmp;
const char* cp=(const char*)ptr;
tmp.data[0] = *cp++;
tmp.data[1] = *cp++;
tmp.data[2] = *cp++;
tmp.data[3] = *cp;
return tmp.n;
}

所以我的问题:

  1. 第二个版本不是未定义的行为吗?C 标准在 6.2.3.2 指针中说,在 7:
指向

对象或不完整类型的指针可以转换为指向其他对象的指针或不完整的类型 对象或不完整的类型。如果生成的指针未正确对齐 57( 为 指向类型,则行为未定义。

由于调用代码在某些时候使用了char*来处理内存,因此必须进行从char*uint32_t*的一些转换。那么,如果uint32_t*没有正确对齐,那不是这种未定义行为的结果吗?如果是,则该函数没有意义,因为您可以编写*(uint32_t*)来获取内存。此外,我想我在某处读到编译器可能期望int*正确对齐,而任何未对齐的int*也意味着未定义的行为,因此为此函数生成的代码可能会做一些快捷方式,因为它可能期望函数参数正确对齐。

  1. 原始代码对参数和所有变量具有volatile,因为内存内容可能会更改(它是驱动程序中的数据缓冲区(无寄存器(。也许这就是为什么它不使用memcpy,因为它不适用于易失性数据。但是,这在哪个世界有意义?如果基础数据可以随时更改,则所有赌注都将关闭。数据甚至可以在这些字节复制操作之间更改。因此,您必须具有某种互斥锁才能同步对此数据的访问。但是,如果您有这样的同步,为什么需要易失性呢?

  2. 对于此内存访问问题,是否有规范/接受/更好的解决方案?经过一番搜索,我得出的结论是,您需要互斥锁,不需要易失性,可以使用memcpy

附言:

# cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 10 (v7l)
BogoMIPS        : 1581.05
Features        : swp half thumb fastmult vfp edsp neon vfpv3 tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc09
CPU revision    : 10

此代码

uint32_t readU32(const uint32_t* ptr)
{
union {
uint32_t n;
char data[4];
} tmp;
const char* cp=(const char*)ptr;
tmp.data[0] = *cp++;
tmp.data[1] = *cp++;
tmp.data[2] = *cp++;
tmp.data[3] = *cp;
return tmp.n;
}

将指针作为uint32_t *传递。 如果它实际上不是uint32_t,那就是UB。 这个论点可能应该是一个const void *.

在转换本身中使用const char *并不是未定义的行为。 根据 C 标准第6.3.2.3指针第 7 段(强调我的(:

指向

对象类型的指针可以转换为指向 不同的对象类型。 如果生成的指针不正确 对齐引用的类型,则行为未定义。
否则,当再次转换回来时,结果应比较 等于原始指针。当指向对象的指针是 转换为指向字符类型的指针,结果指向 对象的最低寻址字节。 的连续增量 结果,最大为对象的大小,生成指向其余对象的指针 对象的字节数。

使用volatile直接在特定硬件上访问内存/寄存器的正确方法将没有规范/接受/最佳解决方案。 任何解决方案都将特定于您的系统,并且超出了标准 C 的范围。

在标准没有的情况下,允许实现定义行为,并且某些实现可能指定所有指针类型具有相同的表示形式,并且可以在彼此之间自由转换,而不管对齐方式如何,前提是实际用于访问事物的指针适当对齐。

不幸的是,由于一些迟钝的编译器强制使用"memcpy"作为 逸出阀出现混叠问题,即使已知指针对齐, 编译器可以有效地处理需要的代码的唯一方法 对对齐存储的与类型无关的访问是假定需要对齐的类型的任何指针将始终适合此类类型。 因此,你直觉认为使用uint32_t*是危险的。 可能需要进行编译时检查以确保函数传递 void* 或 uint32_t*,而不是像 uint16_t* 或 double* 之类的东西,但是如果没有允许编译器通过将字节访问合并到 32 位加载中来"优化"函数,则无法以这种方式声明函数,如果指针未对齐,该加载将失败。

最新更新