C++11 中的严格别名规则
Strict aliasing rule in C++11
我在我的C++11代码中使用了以下C结构(代码来自PostGis的liblwgeom,但这不是问题的核心)。使用 g++-4.8 使用以下选项编译代码:
-std=c++11 -Wall -Wextra -pedantic-errors -pedantic -Werror
而且我在编译过程中没有收到任何错误(或警告)(我应该收到任何错误吗?)
问题
在接受 LWGEOM
且不修改 void *data;
成员的函数中使用 LWPOLY
(实际上由 LWGEOM*
指出)是安全的。我知道这是穷人的遗产,但这是我需要处理的。
详情
多边形:
typedef struct
{
uint8_t type; /* POLYGONTYPE */
uint8_t flags;
GBOX *bbox;
int32_t srid;
int nrings; /* how many rings we are currently storing */
int maxrings; /* how many rings we have space for in **rings */
POINTARRAY **rings; /* list of rings (list of points) */
}
LWPOLY; /* "light-weight polygon" */
LWGEOM:
typedef struct
{
uint8_t type;
uint8_t flags;
GBOX *bbox;
int32_t srid;
void *data;
}
LWGEOM;
点阵列:
typedef struct
{
/* Array of POINT 2D, 3D or 4D, possibly missaligned. */
uint8_t *serialized_pointlist;
/* Use FLAGS_* macros to handle */
uint8_t flags;
int npoints; /* how many points we are currently storing */
int maxpoints; /* how many points we have space for in serialized_pointlist */
}
POINTARRAY;
GBOX:
typedef struct
{
uint8_t flags;
double xmin;
double xmax;
double ymin;
double ymax;
double zmin;
double zmax;
double mmin;
double mmax;
} GBOX;
我这样做是否违反了严格的别名规则?
const LWGEOM* lwgeom;
...
const LWPOLY* lwpoly = reinterpret_cast<const LWPOLY*>(lwgeom);
我知道在 PostGis 中类型被专门设计为 "compatible" 但是我想知道我这样做是否违反了标准。
此外,我注意到 PostGis 编译时默认禁用严格别名(至少版本 2.1.5)。
解决方案
我的同事帮我调查了一下,答案似乎是否定的,它没有违反严格的别名,但只有在我们访问与 LWPOLY 类型相同且布局在结构的开头连续。这就是为什么(引用标准):
3.10.10
表示您可以通过指向 "aggregate or union".
的指针访问成员
8.5.1
定义聚合(C 结构是聚合):
聚合是一个数组或 class(第 9 条),没有用户提供的构造函数 (12.1),没有私有或
受保护的非静态数据成员(第 11 条),无基 classes(第 10 条),无虚函数 (10.3).
9.2.19
表示指向结构的指针与指向标准布局的第一个成员的指针相同 classes(C 结构是标准布局)。
这是否是一种安全的编码方式是另一个问题。
是的,它违反了严格的别名规则。 LWGEOM
和LWPOLY
是不相关的类型,int
和void*
也是。因此,例如,对 lwgeom->data
的修改可能无法通过 lwpoly->nrings
读取,反之亦然。
我用 GCC4.9 验证了这一点。我的代码如下:
#include <cinttypes>
#include <iostream>
using namespace std;
typedef struct {
uint8_t type; /* POLYGONTYPE */
uint8_t flags;
int32_t srid;
int nrings; /* how many rings we are currently storing */
} LWPOLY; /* "light-weight polygon" */
typedef struct {
uint8_t type;
uint8_t flags;
int32_t srid;
void *data;
} LWGEOM;
void f(LWGEOM* pgeom, LWPOLY* ppoly) {
ppoly->nrings = 7;
pgeom->data = 0;
std::cout << ppoly->nrings << '\n';
}
int main() {
LWGEOM geom = {};
LWGEOM* pgeom = &geom;
LWPOLY* ppoly = (LWPOLY*)pgeom;
f(pgeom, ppoly);
}
你猜怎么着,输出是 7。
我在我的C++11代码中使用了以下C结构(代码来自PostGis的liblwgeom,但这不是问题的核心)。使用 g++-4.8 使用以下选项编译代码:
-std=c++11 -Wall -Wextra -pedantic-errors -pedantic -Werror
而且我在编译过程中没有收到任何错误(或警告)(我应该收到任何错误吗?)
问题
在接受 LWGEOM
且不修改 void *data;
成员的函数中使用 LWPOLY
(实际上由 LWGEOM*
指出)是安全的。我知道这是穷人的遗产,但这是我需要处理的。
详情
多边形:
typedef struct
{
uint8_t type; /* POLYGONTYPE */
uint8_t flags;
GBOX *bbox;
int32_t srid;
int nrings; /* how many rings we are currently storing */
int maxrings; /* how many rings we have space for in **rings */
POINTARRAY **rings; /* list of rings (list of points) */
}
LWPOLY; /* "light-weight polygon" */
LWGEOM:
typedef struct
{
uint8_t type;
uint8_t flags;
GBOX *bbox;
int32_t srid;
void *data;
}
LWGEOM;
点阵列:
typedef struct
{
/* Array of POINT 2D, 3D or 4D, possibly missaligned. */
uint8_t *serialized_pointlist;
/* Use FLAGS_* macros to handle */
uint8_t flags;
int npoints; /* how many points we are currently storing */
int maxpoints; /* how many points we have space for in serialized_pointlist */
}
POINTARRAY;
GBOX:
typedef struct
{
uint8_t flags;
double xmin;
double xmax;
double ymin;
double ymax;
double zmin;
double zmax;
double mmin;
double mmax;
} GBOX;
我这样做是否违反了严格的别名规则?
const LWGEOM* lwgeom;
...
const LWPOLY* lwpoly = reinterpret_cast<const LWPOLY*>(lwgeom);
我知道在 PostGis 中类型被专门设计为 "compatible" 但是我想知道我这样做是否违反了标准。
此外,我注意到 PostGis 编译时默认禁用严格别名(至少版本 2.1.5)。
解决方案
我的同事帮我调查了一下,答案似乎是否定的,它没有违反严格的别名,但只有在我们访问与 LWPOLY 类型相同且布局在结构的开头连续。这就是为什么(引用标准):
3.10.10
表示您可以通过指向 "aggregate or union".
8.5.1
定义聚合(C 结构是聚合):
聚合是一个数组或 class(第 9 条),没有用户提供的构造函数 (12.1),没有私有或
受保护的非静态数据成员(第 11 条),无基 classes(第 10 条),无虚函数 (10.3).
9.2.19
表示指向结构的指针与指向标准布局的第一个成员的指针相同 classes(C 结构是标准布局)。
这是否是一种安全的编码方式是另一个问题。
是的,它违反了严格的别名规则。 LWGEOM
和LWPOLY
是不相关的类型,int
和void*
也是。因此,例如,对 lwgeom->data
的修改可能无法通过 lwpoly->nrings
读取,反之亦然。
我用 GCC4.9 验证了这一点。我的代码如下:
#include <cinttypes>
#include <iostream>
using namespace std;
typedef struct {
uint8_t type; /* POLYGONTYPE */
uint8_t flags;
int32_t srid;
int nrings; /* how many rings we are currently storing */
} LWPOLY; /* "light-weight polygon" */
typedef struct {
uint8_t type;
uint8_t flags;
int32_t srid;
void *data;
} LWGEOM;
void f(LWGEOM* pgeom, LWPOLY* ppoly) {
ppoly->nrings = 7;
pgeom->data = 0;
std::cout << ppoly->nrings << '\n';
}
int main() {
LWGEOM geom = {};
LWGEOM* pgeom = &geom;
LWPOLY* ppoly = (LWPOLY*)pgeom;
f(pgeom, ppoly);
}
你猜怎么着,输出是 7。