5

Here is a program that type-casts between pointers of type struct shape, struct rectangle and struct triangle.

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

enum { RECTANGLE, TRIANGLE, MAX };

struct shape {
    int type;
};

struct rectangle {
    int type;
    int x;
    int y;
};

struct triangle {
    int type;
    int x;
    int y;
    int z;
};

struct shape *get_random_shape()
{
    int type = rand() % MAX;
    if (type == RECTANGLE) {
        struct rectangle *r = malloc(sizeof (struct rectangle));
        r->type = type;
        r->x = rand() % 10 + 1;
        r->y = rand() % 10 + 1;
        return (struct shape *) r;
    } else if (type == TRIANGLE) {
        struct triangle *t = malloc(sizeof (struct triangle));
        t->type = type;
        t->x = rand() % 10 + 1;
        t->y = rand() % 10 + 1;
        t->z = rand() % 10 + 1;
        return (struct shape *) t;
    } else {
        return NULL;
    }
}

int main()
{
    srand(time(NULL));

    struct shape *s = get_random_shape();

    if (s->type == RECTANGLE) {
        struct rectangle *r = (struct rectangle *) s;
        printf("perimeter of rectangle: %d\n", r->x + r->y);
    } else if (s->type == TRIANGLE) {
        struct triangle *t = (struct triangle *) s;
        printf("perimeter of triangle: %d\n", t->x + t->y + t->z);
    } else {
        printf("unknown shape\n");
    }

    return 0;
}

Here is the output.

$ gcc -std=c99 -Wall -Wextra -pedantic main.c
$ ./a.out 
perimeter of triangle: 22
$ ./a.out 
perimeter of triangle: 24
$ ./a.out 
perimeter of rectangle: 8

You can see above that the program compiled and ran without any warnings. I am trying to understand if it is valid to type-cast a pointer of struct shape into struct rectangle and vice-versa even though both the structs are of different sizes.

If your answer is that this is not valid, then please consider that network programming books routinely typecast between struct sockaddr *, struct sockaddr_in * and struct sockaddr_in6 * pointers depending on the socket family (AF_INET vs. AF_INET6), and then explain why such type cast is okay in case of struct sockaddr * but not in the above case of struct shape *. Here is an example of type cast with struct sockaddr *.

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int main()
{
    struct addrinfo *ai;

    if (getaddrinfo("localhost", "http", NULL, &ai) != 0) {
        printf("error\n");
        return EXIT_FAILURE;
    }

    if (ai->ai_family == AF_INET) {
        struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
        printf("IPv4 port: %d\n", addr->sin_port);
    } else if (ai->ai_family == AF_INET6) {
        struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr;
        printf("IPv6 port: %d\n", addr->sin6_port);
    }

    return 0;
}

This code compiles and runs fine as well. Moreover, this is the recommended way of writing such programs as per books on socket programming.

$ gcc -std=c99 -D_POSIX_SOURCE -Wall -Wextra -pedantic foo.c
$ ./a.out 
IPv6 port: 20480
Lone Learner
  • 18,088
  • 20
  • 102
  • 200
  • 1
    You are converting a child pointer to a parent pointer and then down-converting it back to the exact corresponding child pointer. You are asking the compiler to trust you (by using casts). So, there are no warnings. – blackpen Sep 10 '16 at 11:13
  • IIRC, this only allowed with `union`s. – edmz Sep 10 '16 at 11:15
  • 1
    Possible duplicate of [Structures and casting in C](http://stackoverflow.com/questions/3846551/structures-and-casting-in-c) – edmz Sep 10 '16 at 11:16
  • @AlterMann @black But that's pretty much how typecasts between `struct sockaddr *`, `struct sockaddr_in *` and `struct sockaddr_in6` work. Why is it okay to typecast `struct sockaddr *` to `struct sockaddr_in6 *` then? – Lone Learner Sep 10 '16 at 11:16
  • @LoneLearner, `sockaddr_in` and `sockaddr_in6` are both structures where first member is a `sockaddr structure`, take a look: http://stackoverflow.com/a/18611136/1606345 – David Ranieri Sep 10 '16 at 11:26
  • @AlterMann `struct sockaddr_in` does not contain any member of type `struct sockaddr`. Instead `struct sockaddr_in` contains a member `sa_family` that matches `sin_family` of `struct sockaddr_in`. It's pretty much a similar case here. `struct shape` has member variables that matches those of `struct rectangle`. So when the type casts are valid there, why not here? – Lone Learner Sep 10 '16 at 11:31
  • @AlterMann The answer you linked to seems incorrect to me. In fact, you can see three comments below [that answer](http://stackoverflow.com/a/18611136/1606345) that point out that `struct sockaddr_in` does not contain `struct sockaddr` as the first member. – Lone Learner Sep 10 '16 at 11:33
  • @LoneLearner, you are right, this answer is not correct and they have only the first two members in common :( yours is a nice question – David Ranieri Sep 10 '16 at 11:35
  • @LoneLearner, read [this](http://www.gta.ufrj.br/ensino/eel878/sockets/sockaddr_inman.html) they claim that it is safe but I don't know how if the standard doesn't say so – David Ranieri Sep 10 '16 at 11:41
  • 1
    @Lone Learner: While what you are doing here might be valid (due to the "common initial sequence" rule), in general case just because "program compiled and ran without any warnings" does not in any way suggest that it is somehow "valid". "Compiles and runs fine" means absolutely nothing about the validity of your code in C world. – AnT stands with Russia Sep 10 '16 at 11:44
  • @AnT So is the program "valid" or "invalid"? That's my question. If the first program is invalid, then why is the second program's way of doing things so popular in socket programming? – Lone Learner Sep 10 '16 at 11:47
  • 1
    @AnT the common initial sequence rule doesn't apply since the structs are not members of a union. It's unclear whether it's a strict aliasing violation: some argue that `r->type` means `(*r).type` and the `*r` violates the rule; others (including me) say that it isn't because `r->type` is the only thing accessed and that is of type `int` and reading an `int`. – M.M Sep 10 '16 at 12:01

6 Answers6

3

The compiler would faithfully diagnose an error if the explicit type conversions were removed from

struct rectangle *r = (struct rectangle *) s;

or from

struct triangle *t = (struct triangle *) s;

The explicit type conversions, in this case, are permitted to work because what is what the standard requires. In effect, by using the explicit type conversion in these two statements you are effectively directing the compiler "shut up, I know what I'm doing".

What is more interesting is why the main() function works at run time, once you have bludgeoned the compiler into submission so it permits the conversion.

The code works because the first member of all three structs are the same type. The address of a struct is equal to the address of its first member, except that the types are different (i.e. a pointer to a struct rectangle has different type from a pointer to an int). Therefore (if we ignore the different types), the test s == &(s->type) will be true. The use of a type conversion deals with that, so (int *)s == &s->type.

Once your code has completed that test, it is then doing an explicit type conversion on s. It happens that, in the declaration

struct rectangle *r = (struct rectangle *) s;

that your code has ensured s is actually the address of a (dynamically allocated) struct rectangle. Hence the subsequent usage of r is valid. Similarly in the else if block, with a struct triangle.

The thing is, if you made an error, such as

if (s->type == RECTANGLE)
{
    struct triangle *t = (struct triangle *) s;
    printf("perimeter of triangle: %d\n", t->x + t->y + t->z);
}

(i.e. using a struct rectangle as if it is a struct triangle) then the compiler will still faithfully permit the type conversion (as discussed above). However, the behaviour is now undefined since s is not actually the address of a struct triangle. In particular, accessing t->z accesses a non-existent member.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • I understand that the programmer has to keep track of the correct types and ensure that only a pointer to a `struct triangle` object is type cast as such. This is pretty much what we do in socket programming as well. See http://beej.us/guide/bgnet/output/print/bgnet_A4.pdf (page 10) - "And this is the important bit: a pointer to a struct sockaddr_in can be cast to a pointer to a struct sockaddr and vice-versa." So is this advice good? Is this common practice in socket programming safe? – Lone Learner Sep 10 '16 at 11:54
  • It is safe if the type conversion is valid (i.e. if the programmer has correctly kept "track of correct types"). It is unsafe (i.e. undefined behaviour) otherwise. – Peter Sep 10 '16 at 12:29
3

Is it legal to type-cast pointers of different struct types (e.g. struct sockaddr * to struct sockaddr_in6 *)?

Yes. C explicitly provides for it:

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.

(C2011, 6.3.2.3/7)

As other answers have pointed out, it is not the cast itself that is the problem, but what you do with the result. And that comes down to the Strict Aliasing Rule:

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,

[... plus several other alternatives that cannot apply in this case ...]

(C2011, 6.5/7; emphasis added)

The main question, therefore, is what is the effective type of the object to which the struct sockaddr * points? It's important here to understand that we can't tell from the declaration of getaddrinfo(), nor that of struct addrinfo. In particular, there is no reason to assume that the effective type is struct sockaddr.

In fact, given that the cast you've asked about is the standard and intended method for accessing the address details, there is every reason to suppose that getaddrinfo() supports that by ensuring that the effective type is the one indicated by the associated ai_family code. Then the corresponding cast yields a pointer matching the effective type of the address info. In that case, there is no problem inherent in accessing the address info via the pointer obtained via the cast.

I observe in support of the above that it is reasonable to suppose that the pointer in question points to a dynamically allocated object. The effective type of such an object depends on the means by which its stored value was last set (C2011, 6.5/6). It is not only plausible but likely that getaddrinfo() would set that value in a manner that gives it the wanted effective type. For example, code along the same lines as your shape example would do so.

Ultimately, casting the struct sockaddr * to and from pointers to the address-family-specific structs is the intended use, and there is no reason to suppose that an environment that provides getaddrinfo() would, in practice, allow those behaviors to be dubious. If it had been necessary, POSIX (by whom the function is specified) could have incorporated a special rule allowing the casts. But no such rule is needed in this case, although POSIX makes you take that on faith.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
2

In the specific case of the Berkeley socket library, the POSIX standard guarantees that you can cast a pointer to a struct sockaddr_storage to a pointer to any type of socket and that the field that identifies the type of socket will map correctly.

Specifically, the POSIX standard specifies of struct sockaddr_storage:

When a pointer to a sockaddr_storage structure is cast as a pointer to a sockaddr structure, the ss_family field of the sockaddr_storage structure shall map onto the sa_family field of the sockaddr structure. When a pointer to a sockaddr_storage structure is cast as a pointer to a protocol-specific address structure, the ss_family field shall map onto a field of that structure that is of type sa_family_t and that identifies the protocol's address family.

It also says of struct sockaddr_in, “Pointers to this type shall be cast by applications to struct sockaddr * for use with socket functions.” The interface of bind(), connect() and so forth can only work if the library looks up the const struct sockaddr* it gets and figures out what type of socket it points to.

A given compiler might need magic to implement that, but this library in particular has to do it for you.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • Could you please add a few references (section number or phrases) to the POSIX standard that explains that this behavior is guaranteed to work? – Lone Learner Sep 11 '16 at 06:16
  • @LoneLearner Done, and made one correction: the standard actually guarantees that about a `sockaddr_storage` structure. – Davislor Sep 11 '16 at 07:04
1

Your question suffers from several terminological mix-ups.

Firstly, just because your program somehow "compiled and ran without any warnings" and even produced the result you expected, it still does not mean that what you are doing in your code is somehow "valid".

Secondly, it appears that you are asking about the validity of the cast itself. In reality the cast itself is beside the point. There's are a lot of things in C that you can "typecast" to each other. However, the language makes no guarantees about what you can do with the results of such casts. The cast itself might be perfectly valid, but your further actions applied to the result might be horribly invalid.

Thirdly, and this is apparently what your question is really about: casting between pointers to different struct types that share a common initial subsequence and then accessing members from that common subsequence through the resultant pointers. It is not the cast that the issue here, it is the subsequent access. And the answer is: no, the language does not define this as a valid technique. The language allows you to inspect the common initial subsequences of different struct types united in a common union, but without a common union this is not allowed.

As for the popular technique with casts between struct sockaddr *, struct sockaddr_in * and struct sockaddr_in6 * - these are just hacks that have nothing to do with C language. They just work in practice, but as far as C language is concerned, the technique is invalid.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Nice answer, so the cast is legal because the first member of the `struct`s are all of the same type, but you can not dereference such pointer, isn't it? – David Ranieri Sep 10 '16 at 12:18
  • Yes, that's the point: the implementation is allowed to use "implementation-specific details" aka dirty hacks. One of them is `transparent_union`s, which indeed allows grouping structs together with an union which is not an union. – edmz Sep 10 '16 at 12:30
  • @AnT I have a follow-up question about this now at http://stackoverflow.com/q/39432774/1175080 – Lone Learner Sep 11 '16 at 06:24
0

It's actually not guaranteed to work. It is guaranteed to work if the compiler sees the declaration of a union which has the three type; it is enough that the compiler sees the declaration. In that case code that accesses common leading elements of the structs is fine. Obviously the most important common element is the "type" member.

So if you have declared a union of struct shape, rectangle and triangle, you can take a pointer which does point to one of the three structs, cast the pointer, access the type field, and then go from there.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • Then do you think that this statement from [bgnet](http://beej.us/guide/bgnet/output/print/bgnet_A4.pdf) is incorrect? `And this is the important bit: a pointer to a struct sockaddr_in can be cast to a pointer to a struct sockaddr and vice-versa.` What's your comment on this claim in bgnet and this way of doing things in socket programming? – Lone Learner Sep 10 '16 at 11:50
  • 1
    The union alternative is not at all guaranteed to work in practice, and it is debatable whether it provides any guarantee even in theory. The *non-normative* footnote that provides the standard's only indication that unions should allow such aliasing seems to conflict with the *normative* provisions of the strict aliasing rule. Moreover, it is debatable whether even the footnote makes any claims about accesses that do not proceed through the union object. – John Bollinger Sep 11 '16 at 06:06
  • @JohnBollinger: The rule about the *complete* union type being visible would be meaningless if such visibility were not intended to compel a compiler to recognize CIS accesses via any of the involved types. Any dialect which cannot support CIS accesses should be regarded as an incompatible offshoot of the language that became popular in the 1990s. – supercat Feb 14 '17 at 01:05
  • @supercat, I take you to be referring to C2011 6.5.2.3/6, providing for access to the objects belonging to the common initial sequence of union members without regard to which member the union actually contains. Although this certainly has implications for storage layout, it does not contradict the strict aliasing rule (6.5/7). You could argue that accessing a structure member via the `->` or `.` operator does not constitute accessing the structure itself, and therefore evades the SAR, but you cannot simply ignore the SAR. – John Bollinger Feb 14 '17 at 05:37
  • 1
    @JohnBollinger: The rule, and its implications with regard to structure pointers, were a fundamental part of C before pointer-aliasing rules were added. The stated purposes of the Standard is to describe an existing language, rather than define a fundamentally new one. The purpose of the C99 language saying that the CIS guarantee only holds when a complete union type is visible is clearly intended to limit the scope of the CIS guarantee, but nothing I see no reason to believe that the majority of Committee members who voted for the aliasing rules understood them as gutting CIS guarantees. – supercat Feb 14 '17 at 13:42
  • @JohnBollinger: Besides--the lvalue expression `structValue.member` doesn't access anything, and is an lvalue of `member`s type--not the `structValue`'s type. A rule that distinguished between "root" and "derived" lvalues and limited the ways things may be derived from root lvalues might be logical and useful (if it included CIS allowance) but I see nothing in the Standard that would impose such a rule. – supercat Feb 14 '17 at 13:50
  • @supercat, whatever the intention of the standardization effort was and is, it is indisputable that C89 differs in some (small) ways from pre-standardization C, and that the language has diverged farther with each subsequent version of the standard. I am prepared to accept that some implementation characteristics are so conventional as to be generally safe to rely upon, but that's quite a different thing from being required by the standard. I am not privy to any committee member's thoughts, but the plain wording of the standard cannot simply be ignored. – John Bollinger Feb 14 '17 at 14:39
  • @JohnBollinger: While the authors of C89 didn't feel a need to expressly say so, the Standard makes no attempt to forbid every unreasonable way in which an implementation may behave. What is reasonable for one target platform and application field may be unreasonable and another, but a quality implementation will strive to behave in a fashion that is reasonable for its intended use. I would guess the authors thought those principles should be self-evident, but some modern compiler writers seem to have completely lost sight of them. If 90% of implementations defined a behavior in some case... – supercat Feb 14 '17 at 14:56
  • ...because their underlying platform did so, but a few platforms would make it impractical to offer any behavioral guarantees, having the Standard impose no requirements would merely preserve the status quo. The notion that classifying such behaviors as "undefined" was intended to reduce the semantics available to programmers on existing platforms seems to be a retroactive reinterpretation. – supercat Feb 14 '17 at 15:11
  • @JohnBollinger: Besides, I'm genuinely curious what you see in the Standard that would regard `structPtr->structMember` as anything other than an lvalue of the member's type? It might make sense for the Standard to impose different requirements when evaluating an lvalue which gets converted to an rvalue, versus evaluating an lvalue that appears on the left half of the assignment, but is there anything in the Standard that actually does so? – supercat Feb 14 '17 at 15:18
  • @supercat, yes, the standard goes to some effort to avoid constraining implementations -- indeed, those efforts have much to do with the concept of undefined behavior in the first place. But the flip side of that coin is that the standard has few qualms about constraining the form and behavior of *programs*, and fewer still about leaving the effects of some behaviors implicitly or explicitly undefined. If there is a modernism that may have gotten out of balance (especially here) it is the premise that undefinedness is an absolute constraint on programmers and programs. – John Bollinger Feb 14 '17 at 15:46
  • @supercat, ... That follows from an emphasis on portability, but where one is prepared to rely on applicable implementation-[class-]specific guarantees, it is not inherently wrong for a program to exercise behavior that their implementation defines but C itself does not. One simply ought to understand that that is what one is doing, and to appreciate the implications. Those unprepared to do so are better off sticking to standard-defined behavior. – John Bollinger Feb 14 '17 at 15:49
  • @supercat, As for `structPtr->structMember`, the issue is not the type of the expression or whether it is an lvalue, but rather whether *evaluating it* has (standard-)defined behavior when the object at the address designated by `structPtr` has a type incompatible with that of the expression `*structPtr`, storage layout notwithstanding. This is a matter of interpretation that I doubt we'll sort out between us, but I maintain that the answer does not depend on what `union` definitions are in scope. – John Bollinger Feb 14 '17 at 16:10
  • @JohnBollinger: The decisions that went into C89 make perfect sense if "Undefined Behaivor" means "punt to the implementer's judgment based upon target platform and application field", but far less sense if it means "forbid programmers from exploiting the behavior even when targeting platforms and fields where the exploiting the behavior could reap benefits vastly greater than the cost of supporting it". Over the years, the costs of supporting various behaviors have increased to the point that it would make sense for implementations to support them only when needed... – supercat Feb 14 '17 at 17:47
  • ...but the solution to that is to define means by which code can indicate when such behaviors are needed, and deprecate code that relies upon such behaviors *without indicating such reliance*. A lot of of code expects to build objects "from scratch" using assignments to `structPtr->structMember`, and such code couldn't work if an lvalue evaluation which is not an "access" required a pre-existing object of the proper type. – supercat Feb 14 '17 at 17:51
-4

But this doesn't work in any language. Also in C++ you should include all the variables in the base class and declare Virtual functions in the base class. Rather to move to shape and than to rectangle , better move to void* and than to rectangle Then this is a Object oriented paradigma. Hinerhitance, polimorphimsum and other are exactly what orientate a language to the objects. To work with object in C, you should hard code. But worth it. I think that the average complexity of the programes doesn't justify to move to C++. There is the difference beetween a Ferrari and truck. At least you doesn't have to work heavy with it, C is funny. At your place I would do this:

typedef enum shape_type{
circle,
rectangle,
triangle,
//...
}S_type;

typedef struct shape
{
   S_type stype;
   int ar_par[4];//default allocated parameters number
   int* p_par; //to default it is going to contain the ar_par address
               //and you are going to change it case you needs more  parameters. You save a malloc more
   int n;//count of parameters
   int (*get_perimeter) (struct shape *);//you can also typedef them
   int (*get_area)(struct shape*);
}*Shape_ptr,Shape;

than to code such this

Shape_ptr new_rectangle(int a, int b)
{
   Shape_ptr res=malloc(sizeof(Shape));
   res->stype=rectangle;
   res->p_par=res->ar_par;//no need to allocate anything
   *res->p_par[0]=a;*res->p_par[1]=b;
   res->n=2;
   res->get_perimeter=get_rectangle_perimeter;
   res->get_area=get_rectangle_area;

}
int get_rectangle_perimeter(Shape_ptr s)
{
   return s->p_par[0]<<1 + s->p_par[1]<<1; //or multiply by two;
}
main() 
{
    Shape_ptr shp =get_random_shape() ; //this function is going to call     new_rectangle
    printf ("shap area is:%d\n",(*shp->get_area)(shp);
}

And so on... This is how you work with objects in C. Object orientated programs, contains some paradigma that in big heavy programs simplify the life of the programmer

jurhas
  • 613
  • 1
  • 4
  • 12
  • It seems you're going around the answer but never providing one. – edmz Sep 10 '16 at 11:59
  • Which is the answer exactly? C allows to cast every pointer to another every kind of pointer. It just hold the address. the code would work also if you cast it to double*, and than you cast again to struct rectangle*. If he wants keep several useless structs, is better cast to void*. Otherweise better improve the structure of the program. – jurhas Sep 10 '16 at 12:04
  • I mean the problem here is conceptual not if it works or less. He is searching something that is not possible to achive with C. It obtains a result, but the concept it fully wrong. – jurhas Sep 10 '16 at 12:07
  • @jurhas But that would mean that almost all socket programs that rely on this behavior is wrong! So do you really mean that the way we do socket programming is incorrect? For example, see page 10 of [bgnet.pdf](http://beej.us/guide/bgnet/output/print/bgnet_A4.pdf) that says, "And this is the important bit: a pointer to a struct sockaddr_in can be cast to a pointer to a struct sockaddr and vice-versa." – Lone Learner Sep 10 '16 at 12:11
  • C is a "middle-level" program language. Exactly in those purpose you can see his "lowlevelness". When you declare a tiped pointer, you are just telling the computer "ok, this random address in the memory his an int. so if I tell you to deferenciate take 4 bytes and treat them as int. If I tell you to move next slot move by 4". The program is not going to control anything, he trust you blindfold. In the program you did you could just get back a pointer and cast it as int. He just think you get back an array of int. (in fact he doesn't know if the next slots are also yours) – jurhas Sep 10 '16 at 14:14
  • Or also cast it as short, than result[1], would get you also the shape tipe. So I would not program like this because, if in the history the programmer required to give a name to each variable is because when the complexity reise there is the risk to get crazy with variable. You can just take n bytes and treat them as you want. It is exactly how malloc() works. But his requirement of the programmer give names and type. and for double and float also to treat the bytes in differents way. – jurhas Sep 10 '16 at 14:21
  • If you would not program like this, how else you would you program the example socket program I have provided in my second code snippet in this question? Also, see the follow-up question at http://stackoverflow.com/q/39432774/1175080. – Lone Learner Sep 11 '16 at 06:38