3

Is it allowed to use a pointer to some type as a pointer to a different type if they have otherwise-identical pointer members that differ only in the constness of the pointed-to values?

Concretely, given the following two structures:

struct type {
  char *a;
   int *b;
};

struct const_type {
  const char *a;
  const  int *b;
};

Is it allowed to treat a pointer to type as a pointer to const_type and vice-versa1, for example passing a type pointer to a function expecting a const_type pointer as shown:

int add(const const_type* t2) {
  return *t2->a + *t2->b;
}

int is_it_legal() {
  int some_int = 42;
  char blah[] = "six times and counting...";
  type t1 = {blah, &some_int};
  return add((const_type*)&t1);
}

My overall motivation here is to have a foo and const_foo struct which differ only in the constness of the objects pointed to by their contained embedded pointers. For any function that doesn't modify the pointed-to data, I would like to write a single function (that takes the const_foo variant) and have it work for both types of objects.


1Evidently in the latter case (using a const_type as a type it is not be safe if it results in an attempt to modify a pointed to value in case they were defined const, but you may assume this doesn't occur).

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • `return t2->a[0] + t2->b;` The resulting type is **not** an int, IMHO. – wildplasser Dec 08 '17 at 00:43
  • I am not going to react. You cannot prove a case with bad code. And the telephone is not an excuse, IMHO – wildplasser Dec 08 '17 at 01:08
  • @wildplasser - I managed to check it compiles now. Cut me some slack, I'm not trying to "prove" any "case", just asking a question! – BeeOnRope Dec 08 '17 at 01:11
  • 1
    You asked for a language lawyer! – wildplasser Dec 08 '17 at 01:13
  • @wildplasser - fair enough: I should probably up my game when I use that tag :) – BeeOnRope Dec 08 '17 at 01:13
  • But the bad news is: the two reactions you got are both based on your *original* question.These people dont read source code, they read intentions. (and copypaste standards for the *assumed* question.) – wildplasser Dec 08 '17 at 01:22
  • @wildplasser - two of the three answers answer perfectly my original and updated question which are equivalent with respect to the underlying question. I don't think the issues with the initalizer, which was just boilerplate to create an object prior to the call of interest, changed it materially at all. I included them only so it would kind of be an MCVE, rather than leaving out the object construction with `...` or whatever (maybe I should have done that). The third answer is too vague to evaluate. – BeeOnRope Dec 08 '17 at 01:26
  • The key here is that 6.2.7 does not state that _type_ and _qualified type_ are compatible types. – Lundin Dec 11 '17 at 15:06

3 Answers3

3

No, the behavior is not defined by the C standard. (Although it is “allowed” in various senses, including that a C implementation may support this as an extension.)

First, C 2011 6.7.3 10 says “For two qualified types to be compatible, both shall have the identically qualified version of a compatible type…” So const char and char are not compatible.

Then 6.7.6.1 says “For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.” So const char * and char * are not compatible.

Next, 6.2.7 1 says “Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: … If both are completed anywhere within their respective translation units, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types;…” Also, it requires “If one is declared with a tag, the other shall be declared with the same tag.” So the structures are not compatible.

Finally, 6.5 7 tells us “An object shall have its stored value accessed only by an lvalue expression that has one of the following types: a type compatible with the effective type of the object, a qualified version of a type compatible with the effective type of the object,…” The first of these does not apply since the structures are not compatible. The second says you can refer to a structure using a const version, which unfortunately does not apply since the const here qualifies the members, not the structure.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Is it legal to `memcpy` from the non-`const` one to the `const` one? – Alex Reinking Feb 20 '23 at 05:17
  • @AlexReinking: Yes, you can `memcpy` from one structure to another. If the structures have the same layout and types except for the `const` qualifiers, this copying will reproduce the values of the source structure in the destination structure. The C standard does not guarantee that two structures that are identical except for qualifiers have the same layout, but they do in every C implementation of which I am aware, and there is no ordinary reason for them to have different layouts. – Eric Postpischil Feb 20 '23 at 13:33
2

It's not even guaranteed to work if the two structs have exactly the same member list. The code is a strict aliasing violation, accessing type1 via an lvalue of type type2.

As always for strict aliasing discussions: the text of the rule is underspecificed, however it's widely accepted that x->y, which is defined as (*x).y, counts as an access of *x for the purposes of strict aliasing. The rationale for this can be seen in this code:

void f(struct A *a, struct B *b)
{
     a->x = 5;
     b->y = 6;
}

The compiler should be free to assume that a->x and b->y cannot alias each other , even if they both happen to have type int, and regardless of their offsets within the struct.

However, if you declare a union containing the two struct types, and that declaration is visible at the point of the attempted aliasing, then this bypasses the strict aliasing rule, even if the objects being aliased are not currently in a union!

See this thread for good coverage of the topic, with commentary about compiler compliance.


Back to your question though: since your structs don't even have the same member list, the text from 6.5.2.3/6 about common initial sequences in unions:

Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.

does not apply; the two types are just unrelated struct types. (The definition of "compatible type" is in 6.2.7 - changing qualifiers makes the types incompatible). And putting them in a union doesn't gain anything.

Finally though, as discussed on the linked thread; what the standard says and what compilers actually do are not the same thing in this area.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Ignoring unions, isn't there _some_ rule that lets you treat a pointer as a pointer to a different type? Maybe it's pointer to a struct can be treated as a pointer to the first member? – BeeOnRope Dec 08 '17 at 00:52
  • 1
    Man I can't wait until all the disputes are sorted out in both languages, maybe in 2030. – BeeOnRope Dec 08 '17 at 00:57
  • @BeeOnRope (rewriting my earlier comment) Yes there is such a rule, 6.7.2.1/15, although I understand that to only applies when the struct pointer is actually pointing to an object of the right type for the pointer – M.M Dec 08 '17 at 01:33
  • Note that the authors of gcc and clang seem to believe that a complete union type declaration is only visible when accesses are made through an lvalue of that type, even though I can find nothing in the Standard that says anything close to that. – supercat Dec 20 '17 at 23:31
1

IMO, I do not think that const matter in the way you want to use it. Also, I think you meant this:

typedef struct type1 {
  char *a;
   int *b;
}type1;

typedef struct type2 {
  const char *a;
  const  int *b;
}type2;


int add(const type2* t2) {
  return t2->a[0] + *t2->b;
}

int is_it_legal() {

  int one = 1;    

  type1 t1 = {"third time the charm?", &one};
  return add((type2*)&t1);
}
sg7
  • 6,108
  • 2
  • 32
  • 40