Strict aliasing rule in C++11

2019-07-20 08:27发布

问题:

I use the following C structs in my C++11 code (the code comes from liblwgeom of PostGis, but this is not the core of the question). The code is compiled with the following options using g++-4.8:

-std=c++11 -Wall -Wextra -pedantic-errors -pedantic -Werror

and I don't get any errors during compilation (or warnings) (should I get any?)

Question

Is safe to use LWPOLY (actually pointed by LWGEOM*) in functions that accept LWGEOM and don't modify the void *data; member. I understand that this is poor man's inheritance but this is what I need to work with.

Details

POLYGON:

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;

POINTARRAY:

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;

Am I violating strict aliasing rule when I do something like?

const LWGEOM* lwgeom;
...
const LWPOLY* lwpoly = reinterpret_cast<const LWPOLY*>(lwgeom);

I know that in PostGis types are specifically designed to be "compatible" however I'd like to know if I am violating the standard by doing so.

Also, I noticed that PostGis is not compiled with strict aliasing disabled by default (at least version 2.1.5).

Solution

My colleague helped me to investigate it and it seems the answer is No it doesn't violate strict aliasing, but only in case we access LWGEOMS members that are of the same type as of LWPOLY's and are laid out in the beginning of the struct contiguously. Here is why (quoting standard):

3.10.10 says that you can access a member through a pointer to "aggregate or union".

8.5.1 defines aggregates (C structs are aggregates): An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

9.2.19 says that pointer to the struct is the same as pointer to the fist member for standard layout classes (C structs are standard layout).

Whether this is a safe way to code is a different question.

回答1:

Yes, it violates the strict aliasing rule. LWGEOM and LWPOLY are unrelated types, and so are int and void*. So, for example, modification to lwgeom->data may not be read through lwpoly->nrings and vice versa.

I validated this with GCC4.9. My code is as follows:

#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);
}

Guess what, the output is 7.