12
votes

According to both C99 §6.2.5p27 and C11 §6.2.5p28:

All pointers to structure types shall have the same representation and alignment requirements to each other.

With a footnote (#39 and #48 respectively):

The same representation and alignment requirements are meant to imply interchangeability as arguments to functions, return values from functions, and members of unions.

(Note that C89 §3.1.2.5 doesn't specify about pointers to structs)

-

It is known that a pointer to void for example, C11 §6.3.2.3p1:

A pointer to void may be converted to or from a pointer to any object type.

Does not imply that a pointer to pointer to void is the same as a pointer to void, and does not imply the same as other pointers to pointers to data objects.

(I apologize if I use the term 'imply the same' too loosely, it is meant to be used only within the specified context)

The following is a code sample demonstrating a generic pointer to pointer to struct:

#include <stdio.h>
#include <stdlib.h>

int allocate_struct(void *p, size_t s) {
    struct generic { char placeholder; };    

    if ( ( *(struct generic **) p = malloc(s) ) == NULL ) {
        fprintf(stderr, "Error: malloc();");
        return -1;
    }

    printf("p:  %p;\n", (void *) *(struct generic **) p);
    return 0;
}

int main(void) {
    struct s1 { unsigned int i;  } *s1;

    if (allocate_struct(&s1, sizeof *s1) != 0)
        return -1;

    printf("s1: %p;\n\n", (void *) s1);

    s1->i = 1;
    free(s1);
    return 0;
}

GCC:

-std=c89 -pedantic-errors -Wall -Wextra -fstrict-aliasing -Wstrict-aliasing=3 -O3

Result: (without warnings)

p:  0x800103a8;
s1: 0x800103a8;

.

The Question

Whether the implied interchangeability of pointers to structs applies to pointers to pointers of structs just as well?

Just to clarify: the question is about struct x ** vs other struct y **, and not about struct x * vs struct y **.

3
it feels super wrong that you have a type struct s1 and an instance that is a pointer. - Simeon Pilgrim
You know you are breaking strict aliasing right? Correct result and no warnings doesn't mean the code is not causing undefined b. - this
+1 for having done your research. - Shahbaz
@self. Hello again, unfortunately the conclusion whether 'strict aliasing' is broken, is not so obvious at this time. (And it is indeed obvious that a result under a specific implementation and no warnings are not a guaranty for being conforming to a specific standard) - Thus, the question :) - Dror K.
@DrorK., I believe C11-§6.5p6 says your code has undefined behavior. It's not very clear to me, but from my understanding, after you store the malloced memory as struct generic *, you shouldn't access it as struct something_else *. - Shahbaz

3 Answers

1
votes

The complete text of §6.2.5p28 in C11 already answers you:

A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. 48) Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

To organize the paragraph a bit (define "==" as "have the same representation and alignment requirements"):

  • void * == char *
  • type-qualifier(s) X * == X *
  • struct X * == struct Y *
  • union X * == union Y *

In the end, the paragraph explicitly says:

Pointers to other types need not have the same representation or alignment requirements.

Since struct X * and struct Y * are two separate types, which are neither char, struct or union, then pointers to them do not necessarily have the same representation and alignment requirements. Therefore, struct X ** and struct Y ** are not guaranteed to have the same representation and alignment requirements.

0
votes

x** is not the same as y** from a type level, the first quote is talking about the size of a pointer as compared to it's type.

A pointer can be cast from x* to void* saying "who cares what this is" so you can pass it around as a address of memory, and your telling the compiler, trusts me I know what I'm doing.

Once you tell the compiler shut-up I know what I'm doing, and you make a mistake, you are on your own.

0
votes

On many platforms with strict aliasing disabled, it is possible to and useful to be able to accept an array of pointers to structures of unknown type; in most cases, such code will also be able to accept an array of pointers to arbitrary types, though some systems may use a different representation for "char" pointers than for pointers to things with coarser alignment.

Unfortunately, the creators of the C89 included a rule, sometimes called the "strict aliasing rule", which has poisoned the language by being ignored long enough by compiler writers and programmers alike that the lack of outcry has led some people to think it can be enforced without causing any problems. In the absence of that rule, code could accept an array of pointers to structs of unknown type whose first field was e.g. an "int" field called id, and write code like item_id = items[index]->id; and have it work efficiently. If one is in the unfortunate position of being unable to avoid aliasing restrictions, one would have to write such code as:

ID_ONLY_ITEM *temp;
memcpy(temp, items+index, sizeof temp);
item_id = temp->id;

I must confess I'm mystified as to why people seem to think there's any reason to have compilers impose strict aliasing, when better optimizations are possible more safely via restrict; I'd suggest that it's better to simply make sure code is documented as requiring strict aliasing and boycott the strict aliasing restrictions.