【问题标题】:GCC considers int pointers unaligned on ARMGCC 认为 ARM 上的 int 指针未对齐
【发布时间】:2013-04-17 23:09:15
【问题描述】:

我有以下测试程序:

#include <string.h>
int q(int *p) {
    int x;
    memcpy(&x,p,sizeof(int));
    x+=12;
    memcpy(p,&x,sizeof(int));
    return p[0];
}

当我使用 GCC 4.7.2 for arm-linux-gnueabihf 编译它时,编译器怀疑指针访问可能未对齐,在程序集输出中注释加载和存储,例如:

    ldr     r0, [r0, #0]    @ unaligned

如果我使用-mno-unaligned-access 编译,编译器根本不会发出直接加载和存储,而是调用库memcpy。但实际上,这种情况下的指针永远不应该是未对齐的。这是 gcc 中的一个忽略,还是我弄错了?

【问题讨论】:

  • 请发布编译后代码的完整反汇编。
  • int q(int *restrict p) { p[0]+=12; return p[0];} 做得更好。 mov r3,r0,ldr r0,[r0],add r0,r0,#12,str r0,[r3],blx。你为什么要编码这个?确实应该对齐。正如其他人所指出的,您将它与memcpy() 混淆了。至少,x = p[0]; 似乎是半理智的。你为什么使用memcpy() 作为作业?下一个人会出现并宣布*p 是字节交换版本。也许更大的问题会有所帮助?
  • 使用memcpy的要点是要兼容严格的别名规则。给定的int * 指针可能派生自指向另一种类型的指针。
  • @Juho 嗯,这正是gcc 生成memcpy 的原因,因为如果int*p 是伪装的char*p,那么它可能根本没有对齐。所以我什至不确定编译器是否可以决定(除了整个程序分析)int*p 是对齐的。考虑到你的动机gcc 是对的。
  • 如果一个指针被强制转换为int*,这是对编译器的一个承诺,即结果是正确对齐的。编译器不需要支持通过int* 进行非对齐访问,例如,gcc 已经为此提供了打包结构。

标签: c gcc arm memory-alignment


【解决方案1】:

我认为gcc 确实被int* 在对memcpy 的调用中被强制转换为void* 感到困惑,并假设这样的指针最坏。它本可以尝试查看底层指针是否正确对齐。您是否尝试过更高的优化级别?可能在更高的级别gcc 变得更聪明。

也有可能gcc 不保证其所有代码中的int 指针对齐,但这是不明智的并且不太可能。

由于第 6.2.3.2 和 7 条,允许编译器假定 int*p 正确对齐:

指向对象类型的指针可以转换为指向不同对象类型的指针。如果 结果指针未正确对齐 68) 对于引用的类型,行为是 未定义。

Note 68) 是关于正确对齐的传递性。

【讨论】:

  • 我是用-O2和-O3编译的,所以大概不缺优化。问题可能是 gcc 没有为内置函数推断指针对齐的机制。
【解决方案2】:

这是我想出的解决方案,实现了一些数据字段访问的替代方案:

// #define USE_MEMCPY
// #define USE_PACKED
#ifdef __cplusplus
template <typename T> void SET(T *__attribute__((may_alias)) p, T val) {
    *p=val;
}
template <typename T> T GET(T *__attribute__((may_alias)) p) {
    return *p;
}
#else
#ifdef USE_MEMCPY
#include <string.h>
#define _SET(p,val,line) \
  ({ typeof(val) _temp_##line = (val); \
       memcpy((void*)(p),(void*)&_temp_##line,sizeof(_temp_##line)); }) 
#define _GET(p,line) \
  ({ typeof(*(p)) _temp_##line; \
       memcpy((void*)&_temp_##line,(void*)(p),sizeof(_temp_##line)); \
       _temp_##line; })

#define SET(p,val) _SET(p,val,__LINE__)
#define GET(p) _GET(p,__LINE__)
#else /* no memcpy */
#ifdef USE_PACKED
#define SET(p,val) (((struct { typeof(val) x __attribute__((packed)); } __attribute__((may_alias))*)p)->x=(val))
#define GET(p) (((struct { typeof(*p) x __attribute__((packed)); } __attribute__((may_alias))*)p)->x)
#else
#define SET(p,val) (*((typeof(val) __attribute__((may_alias))*)p)=(val))
#define GET(p) (*((typeof(*p) __attribute__((may_alias))*)p))
#endif
#endif
#endif

然后我可以这样写函数:

int q(int *p) {
    SET(p,GET(p)+12);
    return p[0];
}

【讨论】:

  • 您可能还需要对齐(1),如__attribute__((may_alias, aligned(1))),告诉编译器类型未对齐以及可能存在别名。你应该可以typedef int __attribute__((may_alias, aligned(1))) unaligned_int;
【解决方案3】:

在 C 编译器中,没有什么比加载和存储 int 值更好的优化了,这在设计上是机器的自然大小。

把函数写成

int q(int *p) {
    return *p += 12;
}

这避免了对库例程的两次调用,否则您将指望优化器内联并减少到简单的加载和存储,并表达就地修改整数值参数并返回结果的意图。

使用memcpy 分配整数会混淆意图。

如果这个问题是将一个更大的问题简化为最小规模的混淆示例的结果,那么我的实现可能不会直接帮助。但即使p 的类型是some_complex_struct * 而不是int *,该建议仍然适用。赋值运算符有效。在有意义的地方优先使用它而不是 memcpy。

【讨论】:

    【解决方案4】:

    如果你的 linux 内核版本在 2.6.28 之前。 GCC 会抛出这个Warning。 -munaligned-access 支持访问未对齐地址上的内存。这需要这些系统的内核启用此类访问 或者,不支持未对齐的访问,所有代码都必须使用 -mno-unaligned-access 进行编译。上游 Linux 内核版本自动且无条件地支持由 GCC 发出的未对齐访问,因为此选项自 2.6.28 版以来一直处于活动状态。

    【讨论】:

    • 我猜他的意思是warning,而不是error。请注意,2.6.28+ 内核将为不支持未对齐加载/存储的处理器提供alignment trap,这将是 1000 条指令,而对齐则需要 1-3 个周期。
    猜你喜欢
    • 2017-01-11
    • 2021-12-05
    • 2015-11-10
    • 2010-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多