ARM上未alignment内存访问的函数

我正在从内存中读取数据的项目。 这些数据中的一部分是整数,在访问未alignment的地址时出现问题。 我的想法是使用memcpy的,即

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

我find的项目源的解决scheme此代码类似

 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:

指向对象或不完整types的指针可能会转换为指向不同对象或不完整types的指针。 如果生成的指针未正确alignment(57)指向types,则行为是不确定的。

由于调用代码在某些时候使用了char*来处理内存,因此必须将char*转换为uint32_t* 。 不是那个未定义的行为的结果,那么,如果uint32_t*没有正确alignment? 如果是的话 ,这个函数没有意义,因为你可以写*(uint32_t*)来获取内存。 此外,我认为我读的地方,编译器可能会期望一个int*正确alignment,任何未alignment的int*也意味着未定义的行为,所以生成的代码为这个函数可能会做一些捷径,因为它可能期望函数参数正确alignment。

  1. 原始代码在参数和所有variables上都是不稳定的,因为内存内容可能会改变(这是驱动程序内部的数据缓冲区(无寄存器))。 也许这就是为什么它不使用memcpy,因为它不会在易失性数据上工作。 但是,在哪个世界是有道理的呢? 如果底层数据随时可能发生变化,所有投注都将被closures。 数据甚至可以在这些字节复制操作之间改变。 所以你将不得不有一些互斥体来同步访问这些数据。 但是如果你有这样的同步,为什么你需要易变的?

  2. 这个内存访问问题是否有一个规范的/被接受的/更好的解决scheme? 经过一番search,我得出结论,你需要一个互斥体,不需要易失性,可以使用memcpy

PS:

 # 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 *不是未定义的行为。 根据6.3.2.3 C标准第7段指针 (重点是我的):

指向对象类型的指针可能会转换为指向不同对象类型的指针。 如果生成的指针未正确对齐引用类型,则行为是不确定的。
否则,当再次转换时,结果应与原始指针相等。 当一个对象的指针被转换为一个指向字符类型的指针时,结果指向该对象的最低寻址字节。 连续递增的结果,直到对象的大小,产生指向对象的剩余字节的指针。

关于正确的方式使用volatile来直接访问内存/寄存器将不具有规范/可接受/最佳解决方案。 任何解决方案将是特定于您的系统,超出了标准C的范围。

实现允许在标准没有的情况下定义行为,并且一些实现可以指定所有的指针类型具有相同的表示,并且可以自由地在彼此之间进行转换,只要实际用于访问事物的指针是适当对齐。

不幸的是,由于一些编译器强迫使用“memcpy”作为别名问题的逃生阀,即使已知指针已经对齐,编译器能够有效处理需要对对齐存储进行类型无关访问的代码的唯一方法是假定任何需要对齐的类型的指针总是适合这种类型的。 因此,你本能的使用uint32_t*是危险的。 可能需要进行编译时检查,以确保函数是通过void *还是uint32_t *,而不是像uint16_t *或double *那样,但是没有办法在不允许的情况下声明函数一个编译器,通过将字节访问整合到一个32位的负载中来“优化”这个函数,如果指针没有对齐的话就会失败。