简体   繁体   中英

Could this use of malloc lead to an “optional” struct field?

I was implementing a structure in which I needed (at runtime) to have an optional field.
So I thought about this:

//...
#include <stdlib.h>

struct test {
  int x;  // Must be
  int y;  // Optional (Must be the last field..(?))
};

int main(int argc, char **argv) {
    // With the optional field
    struct test *a = malloc(sizeof(*a));
    a->x = 11;
    a->y = 55;
    
    // Without the optional field
    struct test *b = malloc(sizeof(*b) - sizeof(int));
    b->x = 22;
    
    // ...
    
    free(a);
    free(b);
    return 0;
}

Could this code do what I ask?
Possibly adding a bit field to check if there is the optional field or not.

Also, if the proposed solution works, if this were implemented for a list of multiple items (> 100000), would it be better to do it to save memory?

Could this code do what I ask?

Well, it could , but you cannot rely on that. Do not do this; it is not a way to write correct programs.

When you write b->x = 22; , the compiler is entitled to behave as if there were a whole struct test at b . You may be thinking, “I am just putting 22 in the bytes for the member x ,” but the compiler may use a “store eight bytes” instruction:

  • Consider some architecture where memory is organized into eight-byte groups. The bus can only read and write whole eight-byte chunks.
  • Since there is no way to write four bytes in hardware, writing four bytes to memory requires reading eight bytes, manipulating them in processor registers to insert the desired values in four of the bytes, and writing eight bytes back to memory.
  • The compiler wants to optimize b->x = 22; , and it knows y has not been set yet, so it is allowed to have any value. So, instead of using an inefficient write-four-byte sequence, the compiler generates an eight-byte store that puts 22 in b->x and 0 in b->y .

Then this fails because the compiler has just written 0 to memory that might be in use for something else because it is not part of the space you allocated for b .

“If you lie to the compiler, it will get its revenge.” — Henry Spencer

What you're attempting doesn't conform to the C standard because you're attempting to use an object of type struct test that doesn't have enough memory allocated for it, even though you're only accessing the fields for which memory was allocated. It might work but you can't rely on that.

What you can do is make use of a flexible array member :

struct test {
  int x;
  int y[];
};

In a struct like this, sizeof(struct test) doesn't include the last member. You can use such a struct by allocating space for the struct plus as many array elements of the last member that you want. For example:

struct test *b = malloc(sizeof(*b) + sizeof(int));
b->x = 1;
b->y[0] = 2;

You'll need to use array indexing to access the last member, but this is a way to do what you want in a standard-conforming manner.

Then in the case you don't want the last member, you do this:

struct test *b = malloc(sizeof(*b));
b->x = 1;

I think your proposed solution is dangerous. Use two different structs:

struct test_x {
   int x;
};

struct test_xy {
  int x;
  int y;
};

Either have two arrays or store a void * to either along with a discriminator (tagged pointer for instance). The other option is use a pointer for the optional element but sizeof(int *) is the same as sizeof(int) at least on my box, so that only makes things larger.

Consider a column layout if all the y members are optional, or you can sort the data so all the xy elements comes first:

struct test_column {
  int *x;
  int *y;
};

struct test_column t = {
  .x = malloc(100000 * sizeof(int)),
  .y = 0

It doesn't help you in case but unions are the standard way to two structs share memory so size of each element is max(sizeof(test_xy), sizeof(test_x)) instead of sizeof(test_xy) + sizeof(test_x).

Finally, consider compression especially if you use the test_column format.

I state that I am not an expert in C, but I am studying it.
I was implementing a structure in which I needed (at runtime) to have an optional field.
So I thought about this:

//...
#include <stdlib.h>

struct test {
  int x;  // Must be
  int y;  // Optional (Must be the last field..(?))
};

int main(int argc, char **argv) {
    // With the optional field
    struct test *a = malloc(sizeof(*a));
    a->x = 11;
    a->y = 55;
    
    // Without the optional field
    struct test *b = malloc(sizeof(*b) - sizeof(int));
    b->x = 22;
    
    // ...
    
    free(a);
    free(b);
    return 0;
}

Could this code do what I ask?
Possibly adding a bit field to check if there is the optional field or not...


Also, if the proposed solution works, if this were implemented for a list of multiple items (> 100000), would it be better to do it to save memory?


Thanks everyone, in advance.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM