Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to cast a C struct to another with fewer elements?

I'm trying to do OOP on C (just for fun) and I've come up with a method to do data abstraction by having a struct with the public part and a larger struct with the public part first and then the private part. This way I create in the constructor the whole struct and return it casted to the small struct. Is this correct or could it fail?

Here is an example:

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

// PUBLIC PART (header)
typedef struct string_public {
    void (*print)( struct string_public * );
} *string;

string string_class_constructor( const char *s );
void string_class_destructor( string s );

struct {
    string (*new)( const char * );
    void (*delete)( string );
} string_class = { string_class_constructor, string_class_destructor };


// TEST PROGRAM ----------------------------------------------------------------
int main() {
    string s = string_class.new( "Hello" );
    s->print( s );
    string_class.delete( s ); s = NULL;
    return 0;
}
//------------------------------------------------------------------------------

// PRIVATE PART
typedef struct string_private {
    // Public part
    void (*print)( string );
    // Private part
    char *stringData;
} string_private;

void print( string s ) {
    string_private *sp = (string_private *)( s );
    puts( sp->stringData );
}

string string_class_constructor( const char *s ) {
    string_private *obj = malloc( sizeof( string_private ) );
    obj->stringData = malloc( strlen( s ) + 1 );
    strcpy( obj->stringData, s );
    obj->print = print;
    return (string)( obj );
}

void string_class_destructor( string s ) {
    string_private *sp = (string_private *)( s );
    free( sp->stringData );
    free( sp );
}
like image 726
Josu Goñi Avatar asked Mar 10 '15 16:03

Josu Goñi


1 Answers

In theory, this could be unsafe. Two separately-declared structs are allowed to have different internal arrangements, as there's absolutely no positive requirement for them to be compatible. In practice, a compiler is highly unlikely to actually generate different structures for two identical member lists (unless there's an implementation-specific annotation somewhere, at which points the bets are off - but you'd know about this).

The conventional solution is to take advantage of the fact that a pointer to any given struct is always guaranteed to be the same as the pointer to that struct's first element (i.e. structs do not have leading padding: C11, 6.7.2.1.15). That means that you can force the leading elements of two structs to be not only the same, but strictly compatible, by using a value struct of a shared type in the leading position for both of them:

struct shared {
    int a, b, c;
};
struct Foo {
    struct shared base;
    int d, e, f;
};
struct Bar {
    struct shared base;
    int x, y, z;
};

void work_on_shared(struct shared * s) { /**/ }

//...
struct Foo * f = //...
struct Bar * b = //...
work_on_shared((struct shared *)f);
work_on_shared((struct shared *)b);

This is perfectly compliant and guaranteed to work, because packing the shared elements into a single leading struct means that only the position of the leading element of Foo or Bar is ever explicitly relied upon.


In practice alignment isn't likely to be the problem that bites you. A much more pressing concern is aliasing (i.e. the compiler is allowed to assume pointers to incompatible types do not alias). A pointer to a struct is always compatible with a pointer to one of its member types, so the shared base strategy will give you no problems; using types that the compiler isn't forced to mark as compatible could cause it to emit incorrectly optimised code in some circumstances, which can be a very difficult Heisenbug to find if you aren't aware of it.

like image 78
Leushenko Avatar answered Oct 25 '22 13:10

Leushenko