Do pointers to pointers to structs have implied interchangeability?
Asked Answered
B

3

12

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 **.

Bobettebobina answered 6/6, 2014 at 12:0 Comment(11)
it feels super wrong that you have a type struct s1 and an instance that is a pointer.Mucoviscidosis
You know you are breaking strict aliasing right? Correct result and no warnings doesn't mean the code is not causing undefined b.Hexastyle
+1 for having done your research.Exception
@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 :)Bobettebobina
@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 *.Exception
@DrorK. Here is why it is broken. You take some memory, and write type A to the memory. That is ok assuming the memory large enough and is aligned properly. But then you interpret that memory as a type B without writing a type B to that memory. This applies to every type including pointers.Hexastyle
@self.: I don't know if you recall, but in the previous question you suggested exactly to the contrary: stackoverflow.com/questions/24002093 ... when it comes to compatibility of the object of the pointee, there's no question. But when it comes to the compatibility of the pointer type for assignment- this is a very different aspect which is unrelated to 'traditional' strict aliasing concerns. I say let's come to a conclusion regarding pointers to pointers to structs first, then we'll sort strict-aliasing... divide and conquer, what do you say? :)Bobettebobina
@DrorK. I don't know if you recall, but in the previous question you suggested exactly to the contrary I'm sorry I don't see me saying anything contrary. You break strict aliasing in both. Your examples are identical as far as the behaviour goes.Hexastyle
I can't think of any way in which struct x ** could have different rep from struct y ** but the compiler would still be conformingLakia
@MattMcNabb: Hello, I was wondering if there's something in-specific that led you to this conclusion? And whether it aligns with the issue of: ...and does not imply the same as other pointers to pointers to data objects.?Bobettebobina
@DrorK well, sizes of structs must be known, so struct X; struct Y; struct S { struct X **ptr; };, we must know about struct X ** before seeing the definition of struct X. Therefore the decision about rep/alignment can only be based on the label X (e.g.: lets say if there's a even number of letters in X then sizeof(struct X**) is 5, else it's 4). Maybe this is actually possible on second thoughts; I was thinking you could then define struct X and struct Y to be the same and put them in a union (meaning they must also have the same layout) but that doesn't make any differenceLakia
E
1

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.

Exception answered 6/6, 2014 at 12:49 Comment(12)
Rather; they are not compatible at all.Hexastyle
@self., they are not mandated by the standard to be incompatible either. The standard says that they need not be compatible. For example, an implementation may have the same representation for all pointers (everything stored as if void *), so everything would be compatible with everything else (a lot of architectures are actually like this).Exception
I think you are using the word compatible incorrectly. Compatible has nothing to do with equal representation. 6.2.7,1: Two types have compatible type if their types are the same.Hexastyle
'Compatibility' is a very strict term which was not used by the standard to describe the pointers to structures, (nor pointers to unions), and not by my question. So the conclusion concerning compatibility is obvious. The question is strictly about implied interchangeability, which by the standard, have very different definitions.Bobettebobina
@self., right. I was using compatible not as a term defined by the standard but rather short for "having the same representation or alignment requirements". I updated the answer not to use the misleading term and please use the definition in this comment when referring to my previous comment.Exception
Allow me to add one more insight from the description: when the description refers to 'void *', it is being described as: "a pointer to void". When it refers to 'char *', it is being described as: "a character type", but when it addresses structs and unions, it seems much more broad (to me): "All pointers to structure types".Bobettebobina
@DrorK., "a character type" means char, signed char and unsigned char (§6.2.p15). While I do find it strange that the standard mandates all struct X *s to have the same representation and alignment, the wording is quite clear that the same doesn't hold for struct X **s. Although given the same representation/alignment of struct X * and struct Y *, I don't see a reason for an implementation to make pointers to them differently. In other words, in practice they are likely to have the same representation/alignment, even though the standard doesn't mandate it.Exception
@Shahbaz: Actually I don't find it strange at all, the standard never imposed a strict specification for representations, this freedom resulted with the inability of void ** to act as a generic pointer to pointer. If the conclusion is that the implied interchangeability is applied- it means that now it is possible to have a generic pointer to pointer to structs, and a generic pointer to pointer to unions. So I find it very much relevantBobettebobina
@DrorK., what I found strange is why the standard wants all struct X * to have the same representation. Probably there is another part of the standard that needs this assumption. Out of curiosity, what benefit do you take from having a generic pointer to pointer to struct? (And what type do you expect it to have anyway?)Exception
@Shahbaz: Thanks for your interest, I think that my previous question shows the limitation: stackoverflow.com/questions/24002093Bobettebobina
@DrorK., good question. I'm afraid interchangeability of struct X **s would not solve your problem either (since your solution won't work for all types, which is a minus (your "generic" function becomes like Java: for some types yes, for some types no)). Furthermore, there is no way to say struct anything ** and have any struct X ** automatically convert to it.Exception
@Exception the rationale for requiring all struct X * to have the same rep etc. is so that the compiler can cope with struct X *ptr; in a struct while struct X is still an incomplete type.Lakia
M
0

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.

Mucoviscidosis answered 6/6, 2014 at 12:41 Comment(1)
I apologize, but I find this answer not to be very informative.Bobettebobina
S
0

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.

Staircase answered 13/10, 2015 at 5:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.