How to do one line assignment to malloc() arrays in C?
Asked Answered
H

4

10

In C I can initialize an array on the stack like so:

SOME_DATA_TYPE* x = (SOME_DATA_TYPE[5]) {v1, v2, v3, v4, v5};

Is there a similar one-line method to assign values to a malloc()-ed array on the heap?

SOME_DATA_TYPE* y = malloc(sizeof(SOME_DATA_TYPE)*5);  
// what comes here?  

Or do I have to iterate over the array and assign values individually?

Houri answered 27/7, 2016 at 19:15 Comment(8)
What about *y = (SOME_DATA_TYPE[5]) {v1, v2, v3, v4, v5};?Kujawa
malloc just wants a size. calculate the size of your nested structure and ask for that many bytes.Hardtop
@Kujawa The right side is interpreted as stack memory. I know this because free(y) then crashes my application.Houri
If you mean x: that is not an array! If it was an array, it would not be called "pointer".Parity
Then you could use memcpy(y, (SOME_DATA_TYPE[5]){v1, v2, v3, v4, v5}, sizeof(SOME_DATA_TYPE)*5);...Kujawa
why do want to dab with one liners? It is better to use more than one line of code for clarity and maintainability.Ignoble
I find it convenient when I'm testing out new code/features for the first time. Plus I'd like to know if there is a maintainable/readable one-liner available out there. :)Houri
If you wish to test something specific, make a small function and stick everything in there. Having it’s a one-liner as a metric just gets in the way of writing readable, useful code.Condemnatory
I
7

The first issue about "initializing" the result of malloc() is that the allocation may fail. Here y is initialized to some pointer value. The data referenced is still indeterminate.

#define  element_count  5
SOME_DATA_TYPE *y = malloc(sizeof *y * element_count);
if (y == NULL) Handle_OutOfMemory();

With C11, code can use compound literals to set, not initialize, the data pointed to by y.

memcpy(y, (SOME_DATA_TYPE[element_count]) {v1, v2, v3, v4, v5}, sizeof *y * element_count);

Using a direct one-liner without checking the allocation would not be robust programming.

// one-liner, but not robust code
SOME_DATA_TYPE *y = memcpy(malloc(sizeof *y * element_count), 
    (SOME_DATA_TYPE[element_count]) {v1, v2, v3, v4, v5}, sizeof *y * element_count);

Notice code uses the sizeof *pointer_variable * element_count rather than sizeof (pointer_variable_dereferenced_type) * element_count as easier to code, less error prone, easier to review and maintain. Either approach will work.

Iconoduly answered 27/7, 2016 at 19:48 Comment(4)
This code is incorrect - it may call Handle_OutOfMemory() if the array size is zero.Davao
@Davao Good eye. Yes, for a general test of the result of malloc(), code should use if (y == NULL && element_count > 0) Handle_OutOfMemory(); Of course, as OP wants "initialize an array", an array size of 0 has other problems beyond malloc() like SOME_DATA_TYPE[element_count]. I would have coded the element_count > 0 test had element_count been a variable.Iconoduly
It'd be possible to check the malloc result in the one-liner with some abuse of the comma operator... certainly would take us out of the bounds of "maintainable/readable" thoughKilldeer
@Killdeer Agreed. Yet it sounds like OP is primary doing this for test purpose anyways, so many OOM it not a concern?Iconoduly
A
4

You could define a function like so:

#include <stdarg.h>
#include <stdlib.h>

int * intMallocInit (int length, ...) {
   // Initialize Variable Arguments
   va_list args;
   va_start (args, length); 

    // Allocate Memory
    int * arr = malloc (sizeof(int) * length);
    if (arr == NULL) return NULL;

    // Initialize Array
    for (int i = 0; i < length; i++) {
        arr[i] = va_arg(args, int);
    }

    // Clean Up and Return
    va_end(args)
    return arr;
}

And we could call it like so:

int len = 3;
int * myArray = intMallocInit (len, 4, 5, 2);
if (myArray == NULL) return 0; 

for (int i = 0; i < len; i++) {
    printf("%d,", myArray [i]);
}

Output: 4,5,2,

Note: We could also define floatMallocInit (), etc. simply by replacing every instance of "int" in the above function to "float"

You could also use void pointers, or enum& switch, but I wont go there.

Untested attempt at general function Im sure theres a clever work around fhe switchs but this is what I can come up with.

#include <stdarg.h>
#include <stdlib.h>
enum {INT, FLOAT /* etc...*/}

 int getSize(int type) {
     int typeSize = 0;
     switch (type) {
        case INT: 
             typeSize = sizeof (int);
             break;
        case FLOAT:
             typeSize = sizeof (float);
             break;
        // Etc...
        default:
             return -1;
    }
    return typeSize;
 }


void * mallocInit (int type, int length, ...) {
   // Initialize Variable Arguments
   va_list args;
   va_start (args, length); 

    // Get Size of type
    int typeSize = getSize(type);
    if (typeSize == -1) return NULL;

    // Allocate Memory
    void * arr = malloc (typeSize * length);
    if (arr == NULL) return NULL;

    // Initialize Array, Maybe va_copy can be used? No good reference
    for (int i = 0; i < length; i++) {
         switch(type) {
            case INT:
             arr[i] = va_args(args, int);
             break;
            // Etc..
        }
    }

    // Clean Up and Return
    va_end(args)
    return arr;
}

Called with

float myArray = mallocInit (FLOAT, 3, 1, 5, 7);
Aucoin answered 27/7, 2016 at 19:40 Comment(6)
This is a really nice solution!Kujawa
Sorry, but how what does type mean? I never used ...as of right now, could you please briefly explain how it works?Bildungsroman
I modified it to make it more clear. The ... represents a variable number of parameters. (You have to include <stdarg>) Check tutorialpoint it has a good reference.Aucoin
arr[i] = args[i];?!?! That doesn't even compile. At best, that's non-standard. You need to use arr[i] = va_arg(args, int);.Washery
for (int i = 0; i < length; i++;) does not compile, its va_arg, not va_args, and it should be va_end (args); not va_end. (otherwise, nice function)Beamends
arr[i] = va_arg(args, void); --> ``void` is not the right typeIconoduly
M
2

One way to do it without getting too far away from the natural C memory management philosophy, is to use a memdup-like function. Since it is not standard, you might have to implement it yourself

SOME_DATA_TYPE *y = memdup(
  (SOME_DATA_TYPE[5]) {v1, v2, v3, v4, v5},
  5 * sizeof(SOME_DATA_TYPE)); 
Mv answered 27/7, 2016 at 19:56 Comment(1)
Hmmm, good to know. LSNED. Perhaps could use a #define MEMDUP(x) (memdup((x), sizeof (x))) to simply the call to MEMDUP((SOME_DATA_TYPE[5]) {v1, v2, v3, v4, v5})?Iconoduly
A
0

I'm a bit late to the party but i like some type safety on compile time, so:

void* _mallocInit(int type_size, int size, void* init_list) {
  void* mem = malloc(size * type_size);

  if (mem == NULL) return mem;

  memcpy(mem, init_list, type_size * size);

  return mem;
}

#define NUMARGS(actualType, ...)  (sizeof((actualType[]){__VA_ARGS__})/sizeof(actualType))

#define mallocInit(actualType, ...)\
  (actualType*)_mallocInit(sizeof(actualType), NUMARGS(actualType, __VA_ARGS__), (actualType[]){__VA_ARGS__})

And an example that warns on compile time:

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

void* _mallocInit(int type_size, int size, void* init_list) {
  void* mem = malloc(size * type_size);

  if (mem == NULL) return mem;

  memcpy(mem, init_list, type_size * size);

  return mem;
}

#define NUMARGS(actualType, ...)  (sizeof((actualType[]){__VA_ARGS__})/sizeof(actualType))

#define mallocInit(actualType, ...)\
  (actualType*)_mallocInit(sizeof(actualType), NUMARGS(actualType, __VA_ARGS__), (actualType[]){__VA_ARGS__})

typedef struct {
  uint32_t id;
  char* smt;
} foo_t;

int main() {
  // works if .smt=1 is replaced by .smt=NULL
  foo_t *l = mallocInit(foo_t,{ .id=1, .smt=mallocInit(char, "opa") }, { .id=10, .smt=1 });
  printf("%d %d\n", l[0].smt, l[1].smt );
  free(l); // leaking .smt memory, deal with it
}

The warning is pretty good as well:

test.c: In function ‘main’:
test.c:28:87: warning: initialization of ‘char *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   28 |   foo_t *l = mallocInit(foo_t,{ .id=1, .smt=mallocInit(char, "opa") }, { .id=10, .smt=1 });
      |                                                                                       ^
Adaxial answered 15/8 at 3:34 Comment(1)
Identifiers ending in _t are reserved by POSIX, alas.Condemnatory

© 2022 - 2024 — McMap. All rights reserved.