Ruslanas Balčiūnas
Ruslanas Balčiūnas

Reputation: 7418

How to "extend" struct in C?

I want to read different data structure from file based on some value. In some interpreted languages I could do something similar to code below. In C++ I would extend WindowsOptions class. However for some reason I chose C. I can write two separate code blocks for each case. Do I have any other options? This question may be stupid, but what would you expect from that HTML guy.

if(options.signature == 0x20b) {
    PEOptionsHeaderWindowsPlus windowsOptions;
} else {
    PEOptionsHeaderWindows windowsOptions;
}
fread(&windowsOptions, sizeof(windowsOptions), 1, file);
printf("%hu", windowsOptions.MajorOsVersion);

Upvotes: 2

Views: 1727

Answers (3)

user3629249
user3629249

Reputation: 16540

the following suggested code handles either strut size and any error conditions

struct shortStruct
{
    int field1;
};

struct longStruct
{
    int field1;
    int field2;
};

char buffer[sizeof(longStruct)+1];

memset( buffer, '\0', sizeof(longStruct) );
if ( 0x020b == options.signature  )
{
    byteCount = read( fd, buffer, sizeof( struct shortStruct ) );
}
else
{
    byteCount = read( fd, buffer, sizeof( struct longStruct );
}

switch( byteCount )
{
    case sizeof(longStruct):
        // process long struct fields
        break;

    case sizeof(shortStruct):
        // process short struct fields
        break;

    default:
        // process the error
        break;
} // end switch

Upvotes: 0

Arkku
Arkku

Reputation: 42139

Since there can be no padding at the head of a struct, two structs sharing the same (or compatible) first fields are compatible for that part. This trick is used, e.g., by the various sockaddr types (the first field being the address type and the rest depending on the value of that field).

Here is an example of how to use this property in the scenario you describe:

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

#define common_fields \
    int value; \
    bool is_plus

struct base_type {
    common_fields;
};

struct plus_type {
    common_fields;
    int plus_value;
};

static void print_struct (const struct base_type * const p) {
    (void) printf("value = %d\n", p->value);
    if (p->is_plus) {
        const struct plus_type * const plus = (struct plus_type *)p;
        (void) printf("plus_value = %d\n", plus->plus_value);
    }
}

int main (int argc, char *argv[]) {
    struct base_type *p;
    if (argc > 1) {
        p = malloc(sizeof(struct plus_type));
        p->is_plus = true;
    } else {
        p = malloc(sizeof(struct base_type));
        p->is_plus = false;
    }
    p->value = 6;
    if (p->is_plus) {
        ((struct plus_type *)p)->plus_value = atoi(argv[1]);
    }
    print_struct(p);
    free(p);
    return EXIT_SUCCESS;
}

The common_fields macro is used to avoid typing the common fields twice. Many popular compilers have an extension that would allow doing the following instead:

struct base_type {
    int value;
    bool is_plus;
}

struct plus_type {
    struct base_type;
    int plus_value;
};

With clang and gcc this extension is enabled by -fms-extensions.

Note that you must take care to allocate the proper size of struct when using this trick. Alternatively you could add extra fields (e.g., an array of char) to the end of struct base_type to make it at least as large as struct plus_type (and any other structs that share the same base type), but in that case you may be better off with the union solution.

Upvotes: 2

Nick Louloudakis
Nick Louloudakis

Reputation: 6005

How about wrapping it with another one?

typedef struct structA{
  int x;
  int y;
}A;

typedef struct structB{
  A *a;
  int z;
}B;

Note: remember initializing the A pointer inside structB.

UPDATE: Another option would be to use a union of the two types:

typedef struct structA{
  int x;
  int y;
}A;

typedef struct structB{
  int z;
}B;

union AB {
  A* a;
  B* b;
} ABUnion;

Upvotes: 2

Related Questions