例如,-O3
处的GCC 4.9.0编译此
typedef struct {
double x, y, z;
} vector;
double
vector_get(const vector *v, size_t i)
{
switch (i) {
case 0:
return v->x;
case 1:
return v->y;
case 2:
return v->z;
default:
__builtin_unreachable();
}
}
比较和跳跃(更大的例子得到一个跳转表)。但最佳编译只是
return ((double *)v)[i];
(如果已定义)。显而易见的解决方案是用数组替换单独的vector
字段,但是编写.x
,.y
等的能力极大地提高了代码其他部分的可读性。
答案 0 :(得分:2)
这取决于你想要避免未定义行为的严格程度。
是的,语言标准并不保证向量中的双精度数在内存中彼此相邻,但没有理智的编译器会引入填充。由于您也不打算通过int指针或类似的邪恶事物访问double,因此不应该与严格的别名规则冲突。
因此,从语言律师的角度来看,演员((double*)v)[i]
是未定义的行为,但在实践中,你很可能会逃脱它。
如果你想避免演员表,你也可以像这样取第一个坐标的地址:(&v->x)[i]
如果你想严格遵守语言规则,那你就不走运了。允许编译器在坐标之间添加任何数量的填充(无论是否以任何方式理解),因此通过指针算法访问y
或z
或通过union
是未定义的行为。没有办法解决这个问题,甚至没有memcpy()
的技巧。
如果你进行数组访问,尽管它是技术上未定义的行为,你至少可以通过声明结构的大小来防止填充:
_Static_assert(sizeof(vector) == 3*sizeof(double), "Your compiler inserts padding between doubles, but this code assumes that there is no such padding.");
然而,这仍然不是防弹的,因为UB总是意味着如果优化器足够智能以实现它的UB,则允许优化你的代码。
答案 1 :(得分:2)
您的问题是关于gcc,但您似乎希望尽可能与标准C兼容。
标准C提供了有关给定struct变量中struct成员的allignment属性的一些知识。这允许执行一些操作,比如计算结构中成员之间的偏移量。您需要知道此偏移量才能在"指针模式下执行访问"到一个"结构变量"。
但是,似乎你想要完全相反:访问"指针或数组"好像它是一个" struct"。这不能保证工作,因为结构可以在两个连续元素之间具有填充字节。但是在数组中,可以保证所有连续元素都在内存中而没有任何填充。
我的结论是,一般来说,你要做的事情会让你犯错误 这并不是不可能的,但问题是字节的排列取决于编译器:它无法预测。
我们可以肯定地说:
doubles
,保存在3个double
类型的连续对象的存储空间(内存)中的连续字节块中。 x,y,z
的结构按照成员声明的顺序保存在一个连续的字节块中。所以.x < .y < .z
。 memcpy()
复制,因此,特别是,可以访问这些字节的相对地址(从结构的第一个字节的地址开始)。 .y
的地址不一定是成员地址.x
&#34;加&#34; sizeof(double)
。 填充字节的选择取决于编译器。
因此,使用联合键入punning并不能保证为您提供所期望的可移植结果。
答案 2 :(得分:2)
正如您所建议的那样,用数组(或矢量,如果需要)替换三个成员。然后添加内联访问函数x()
,y()
和z()
,它们分别返回v[0]
,v[1]
和v[2]
(您甚至可以返回引用以允许它们作为l值。
答案 3 :(得分:0)
您可以通过多种方式确定您的结构布局是否适合您的优化,但有一种方法是:
#define X_O offsetof(vector, x)
#define Y_O offsetof(vector, y)
#define Z_O offsetof(vector, z)
const int vector_is_packed
= ((Z_O - Y_O) == (Y_O - X_O)) && ((Y_O - X_O) == sizeof(double));
然后,您可以按如下方式编写代码:
double
vector_get_0 (const vector *v, size_t i)
{
if (vector_is_packed) {
return *(double *)((char *)&v->x + i*sizeof(double));
}
static const size_t delta[] = {
offsetof(vector, x), offsetof(vector, y), offsetof(vector, z)
};
return *(const double *)((const char *)v + delta[i]);
}
死代码优化将留下一条路径或另一条路径,具体取决于平台是否已打包三个双打。
答案 4 :(得分:0)
理想情况下,您只需使用typedef double vector[3];
,这样您的所有代码都可以受益,但如果它是很多代码,您只需用union替换struct(在大多数系统sizeof(double)== 8 so打包和填充不会成为问题, - 如果您正在使用其他平台,请参阅之前对数组的评论)
#include <stddef.h> //for size_t
typedef union __attribute__ ((packed)){
double v[3];
struct { double x, y, z;};
}vector;
double vector_get(vector *v, size_t i) //note the unsigned type - eliminate a conditional branch
{
if (i<3) return v->v[i];
__builtin_unreachable(); //as long as code is not called with i>2 or i<0
}
这编译为:
vector_get(vector*, size_t int):
movsd (%rdi,%rsi,8), %xmm0
ret
这大致相当于#define vector_get(v,i) ((double*)(v))[i]
,因为如果i&gt; 2
__builtin_unreachable()
将生成会造成严重破坏的程序集
根据Agnar Fog的compilation of assembly latency and throughput,这非常快。
指令
---------------
MOVSD
操作数
---------------
R,R
μops
------
1
延迟
-----------
2-3
友情
吞吐量
---------------
2
指令
设置
---------------
SSE
这是针对Pentium4但具有单个操作码,2-3个时钟周期延迟(如果下一条指令在同一执行单元中仅为2)并且能够每2个时钟周期处理1个,则新的改进空间不大CPU生成(仅整数运算更快1个操作码,1个周期,每个周期1/3或3个)。
当然,这只能修复这一功能。你总是可以尝试更激烈的东西:
sed -i "s/->x/[X]/g" *.h *.c #and whatever other paths/files
sed -i "s/.x/[X]/g" *.h *.c
#and so on for y and z and change your typedef to an array and an enum {X,Y,Z};
#the enum will solve your code readability concern
但是你可能希望首先从sed中取出-i并为它们grep,这样你就可以确保不会破坏别的东西:grep "\->" | sed "s/->x/[0]/g"
...只是为了安全起见(我想知道是否有一种工具可以做到这一点?)
另一种选择是使用预定义的宏。功能上没有真正的区别;但是,如果您尝试访问范围之外的数据,您将获得编译时断言(因为它未定义)
#define PREFIX_VECTOR_0(v,n) ((v)[0])
#define PREFIX_VECTOR_1(v,n) ((v)[1])
#define PREFIX_VECTOR_2(v,n) ((v)[2])
#define PREFIX_VECTOR_X(v,n) ((v)[0])
#define PREFIX_VECTOR_x(v,n) ((v)[0])
#define PREFIX_VECTOR_Y(v,n) ((v)[1])
#define PREFIX_VECTOR_y(v,n) ((v)[1])
#define PREFIX_VECTOR_Z(v,n) ((v)[2])
#define PREFIX_VECTOR_z(v,n) ((v)[2])
#define PREFIX_VECTOR_i(v,n) ((v)[n]) //for iterations using i (Note n can be > 2 here)
#define PREFIX_VECTOR_GET(v,n) PREFIX_VECTOR_##n(v,n)
#include <stddef.h>
static inline double vector_get(double *v,size_t i){
return PREFIX_VECTOR_GET(v,i);
}
答案 5 :(得分:-1)
不是C联盟(http://www.tutorialspoint.com/cprogramming/c_unions.htm)你在寻找什么?
typedef union {
struct { __attribute__ ((aligned (1))) double x, y, z } vector,
double components[3];
} VectorView;
double
vector_get(const VectorView *v, size_t i)
{
if (i<0 || i>2) __builtin_unreachable();
v.components[i];
// v.vector.x = ...;
}
(编辑:添加了非便携式gcc 属性((aligned(1)))以修复关于对象评论的潜在错误