Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it okay to store different datatypes in same allocated memory in C?

I want to store different datatypes in the same allocated memory for reducing the execution time by only allocating the memory once. I found out it is actually possible to create an array of uint8_t variables and create a new uint16_t pointer to the same memory address and then I can read the values both ways.

That allows me to create a pointer let's say to the middle of the allocated memory and store the values in the second half in a different datatype.

Is that okay to do so? I know I need to take care with the memory boundaries but is this bad style?

Here my code:

#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>

int main(void){
    uint8_t *array;
    uint16_t *array2;
    array = calloc(6, 1);
    array[0] = 257;

    printf("array[0]= %" PRIu8 "\n", array[0]);
    printf("array[1]= %" PRIu8 "\n", array[1]);
    printf("Adresse von array[0] = %p\n", &array[0]);
    array2 = &array[0];
    printf("Adresse auf die array2 zeigt = %p\n", array2);

    array2[0] = 257;
    printf("array2[0]= %" PRIu16 "\n", array2[0]);
    printf("array2[1]= %" PRIu16 "\n", array2[1]);
    printf("array[0]= %" PRIu8 "\n", array[0]);
    printf("array[1]= %" PRIu8 "\n", array[1]);

    getchar();
    return 0;
}
like image 574
TimFinnegan Avatar asked Dec 31 '15 17:12

TimFinnegan


2 Answers

Use a union to create a variable that sometimes stores one type, sometimes another.

union {
  uint8_t u8;
  uint16_t u16;
} *array_u;
size_t nmemb = 6; 

array_u = calloc(nmemb, sizeof *array_u);
assert(array_u);

printf("array_u[0].u8 = %" PRIu8 "\n", array_u[0].u8);

array_u[0].u16 = 1234;
printf("array_u[0].u16 = %" PRIu16 "\n", array_u[0].u16);
...

This does not use all the space with only one uint8_t u8 per union. The below uses 2 uint8_t.

union {
  uint8_t u8[2];
  uint16_t u16;
} *array_u;

printf("array_u[0].u8[0] = %" PRIu8 "\n", array_u[0].u8[0]);

array_u[0].u16 = 1234;
printf("array_u[0].u16 = %" PRIu16 "\n", array_u[0].u16);

OTOH If code needs to overlay an entire array of fixed length

union {
  uint8_t u8[12];
  uint16_t u16[6];
} *array_u;

array_u = calloc(1, sizeof *array_u);
assert(array_u);

printf("array_u->u8[0] = %" PRIu8 "\n", array_u->u8[0]);

array_u->u16[0] = 1234;
printf("array_u->u16[0] = %" PRIu16 "\n", array_u->u16[0]);
...
like image 175
chux - Reinstate Monica Avatar answered Oct 13 '22 07:10

chux - Reinstate Monica


Is that okay to do so?

The standard says:

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)

Note that you don't even have to use the converted pointer to produce undefined behavior -- the behavior of the conversion itself is undefined.

With that said, your example code exhibits a special case: the pointer returned by a successful call to calloc() (or malloc() or realloc()) is guaranteed to be aligned appropriately for an object of any type, so the specific pointer conversion you perform (array2 = &array[0]) should always be ok. It should, however, require a cast. Your compiler ought to be warning you about that.

If you plan instead to convert pointers to arbitrary positions in the interior of the allocated block, then you cannot rely on the alignment to be correct.

Note also that with this scheme you risk harming performance instead of improving it. In particular, some processors have much better load and store performance for data that are aligned appropriately, even though they can handle other alignment. You pay memory allocation cost once per block -- you pay any cost of misaligned access on every access.

I know I need to take care with the memory boundaries but is this bad style?

Yes, very. You are sacrificing code clarity for an uncertain, hypothetical performance gain. The increased development and maintenance costs are likely to swamp any performance gain, especially since you are obfuscating your intent for the compiler, too, and thereby making it harder to generate efficient machine code.

Moreover, it is probably far too early to start in on this sort of micro-optimization. Make your program work correctly first. If it's not fast enough, test to find the bottlenecks and focus on improving those parts. You are most likely to improve performance by choosing better algorithms; little tricks such as you propose are rarely worth it.

like image 4
John Bollinger Avatar answered Oct 13 '22 07:10

John Bollinger