Can we use single malloc() block to represent two different types?

Viewed 116

I am writing a function where I need to call malloc multiple times to allocate memory for very small arrays dynamically (like 4-5 ints. The combinations and lengths are determined during runtime). These arrays are used to store different types of datatypes. so I thought of allocating a large memory and use it for all types. Here is what I am trying to.

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


int main() {
    
    size_t n_char = 4, n_float =4 ; /* these are not fixed. 
                                    I set this just for demonstrative purposes,
                                    n_char and n_float value are determined
                                    during runtime */
    
    char * a = malloc( n_char*sizeof(char) + n_float*sizeof(float) ); /* Allocating for 4 ints and 4 chars*/
    
    for (int i=0; i<4 ; ++i) a[i] = 'a' ; 
    
    float *b = (void *)(a+4) ; /* can we cast this pointer in this way ?*/
    
    for (int i=0; i<4 ; ++i) b[i] = 1.0f ;  
    
    for (int i=0; i<4 ; ++i) printf("%c" , a[i]) ;
    
    printf("\n");
    
    for (int i=0; i<4 ; ++i) printf("%f  " , (double)b[i]) ;
    
    printf("\n");

    return 0;
}

I am now confused, is this a good allowed practice ?

2 Answers

This is not necessarily a good practice, because you'd need to not only care about the size of b but its alignment requirements. Suppose you only needed 2 chars and you used this code, then your float would be improperly aligned on all currently common platforms.

If you know the sizes during the compilation time, just allocate a structure:

struct foo {
    char c[4];
    float f[4];
}

and the compiler will take care of the rest. Otherwise, if dynamic, and your char array was say 6 elements, you need to round that 6 up to the nearest multiple of alignof(float) (most likely 8). Of course you need to allocate sufficient amount of memory too, i.e. just not the sum of the array elements but account for the padding as well, i.e. suppose you allocate 6 chars, 6 floats, you'd probably end up with 6 chars, 2 padding bytes, 6 * 4 bytes for floats.

The cast itself is thought to be legal, and so is the pointer arithmetic, since all this is pointing to an allocated array with no effective type.


Another way to not bother with alignof is to put the float array first - alignof(char) will be 1, and therefore you can always pack them as tightly as possible.

The compile-time dimensioning of a struct precludes mixing runtime quantities of various datatypes in a single block of memory as returned by a single malloc(). However, a running executable may be able to tally the storage space needed, then allocate and populate a block sufficient to meet the need.

Declaring a union similar to the following forms a datatype that can be used to assign values to sub-regions that are properly aligned in memory.

typedef union {
    char c[8];
    short s[4];
    int i[2];
    float f[2];
    double d;
} t_t;

A single instance of this union will be 8 contiguous bytes that can be used to store 1xdouble, 2xfloats, 2xints... or 8 bytes. (Perhaps the application needs 'unsigned' types.)

The code below allocates (an array of) 5 contiguous instances of this union, then uses both array and pointer syntax to demonstrate writing and retrieving data to/from the block. It is a bit like a 'container' whose diverse contents is known to the code.

void my_main() {
    printf( "sizeof t %d\n", sizeof t_t );

    t_t *x = (t_t *)malloc( 5 * sizeof t_t );

    // x[0] stores an 8byte double
    double *pD = &x[0].d;
    *pD = 365895475.45;

    // x[1] & x[2] store 4x 4byte floats
    float *pF = &x[1].f[0];
    pF[0] = 123.123; pF[1] = 456.456; pF[2] = 789.74; pF[3] = 42.42; 

    // x[3] stores a long string, spilling into x[4]
    char *pC = &x[3].c[0];
    strcpy( pC, "Quick brown fox" );

    printf( "%l.2f %.2f %.2f %.2f %.2f %s.. and '%s'\n",
        *pD, pF[0], pF[1], pF[2], pF[3], pC, &x[4].c );
}

Output:

sizeof t 8
365895475.45 123.12 456.46 789.74 42.42 Quick brown fox.. and 'own fox'

(Notice that the long string spilled-over from x[3] into x[4].)

This is but a prototype... The particular needs of the OP are not known, but this demonstrates one technique.

The curious might consider using the first 8 bytes (or 16) to store both type and quantity of the following instances. One could then, for instance, store perhaps something like "D7I5C" to indicate there are 7 doubles, 5 integers and a null-terminated string in a 'packet'. The dimensions (sizeof & strlen()) and quantities (considering modulo) would define the number of instances required.

Related