Home > Software design >  Is it possible to simulate C99 lvalue array initialization in C90?
Is it possible to simulate C99 lvalue array initialization in C90?

Time:11-19

Context:

I am experimenting with functional programming patterns in C90.

Goal:

This is what I'm trying to achieve in ISO C90:

struct mut_arr tmp = {0};
/* ... */
struct arr const res_c99 = {tmp};

Initializing a const struct member of type struct mut_arr with a lvalue (tmp).

#include <stdio.h>

enum
{
    MUT_ARR_LEN = 4UL
};

struct mut_arr
{
    unsigned char bytes[sizeof(unsigned char const) * MUT_ARR_LEN];
};

struct arr {
    struct mut_arr const byte_arr;
};

static struct arr map(struct arr const* const a,
               unsigned char (*const op)(unsigned char const))
{
    struct mut_arr tmp = {0};
    size_t i = 0UL;

    for (; i < sizeof(tmp.bytes);   i) {
        tmp.bytes[i] = op(a->byte_arr.bytes[i]);
    }

    
    struct arr const res_c99 = {tmp};
    return res_c99;
}

static unsigned char op_add_one(unsigned char const el)
{
    return el   1;
}

static unsigned char op_print(unsigned char const el)
{
    printf("%u", el);
    return 0U;
}

int main() {
    struct arr const a1 = {{{1, 2, 3, 4}}};

    struct arr const a2 = map(&a1, &op_add_one);

    map(&a2, &op_print);

    return 0;
}

This is what I tried in C90:

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

enum {
    MUT_ARR_LEN = 4UL
};

struct mut_arr {
    unsigned char bytes[sizeof(unsigned char const) * MUT_ARR_LEN];
};

struct arr {
    struct mut_arr const byte_arr;
};

struct arr map(struct arr const* const a,
               unsigned char (*const op)(unsigned char const))
{
    struct arr const res = {0};
    unsigned char(*const res_mut_view)[sizeof(res.byte_arr.bytes)] =
        (unsigned char(*const)[sizeof(res.byte_arr.bytes)]) & res;

    struct mut_arr tmp = {0};
    size_t i = 0UL;

    for (; i < sizeof(tmp.bytes);   i) {
        tmp.bytes[i] = op(a->byte_arr.bytes[i]);
    }

    memcpy(res_mut_view, &tmp.bytes[0], sizeof(tmp.bytes));
    return res;
}

unsigned char op_add_one(unsigned char const el) { return el   1; }

unsigned char op_print(unsigned char const el) {
    printf("%u", el);
    return 0U;
}

int main() {
    struct arr const a1 = {{{1, 2, 3, 4}}};

    struct arr const a2 = map(&a1, &op_add_one);

    map(&a2, &op_print);

    return 0;
}

All I do is to create an "alternate view" (making it essentially writable). Hence, I cast the returned address to unsigned char(*const)[sizeof(res.byte_arr.bytes)]. Then, I use memcpy, and copy the contents of the tmp to res.

I also tried to use the scoping mechanism to circumvent initializing in the beginning. But it does not help, since there cannot be a runtime evaluation.

This works, but it is not anything like the C99 solution above. Is there perhaps a more elegant way to pull this off?

PS: Preferably, the solution should be as portable as possible, too. (No heap allocations, only static allocations. It should remain thread-safe. These programs above seem to be, as I only use stack allocation.)

CodePudding user response:

If I understand correctly, you want to compile the first snippet with C89/C90, isn't it?

In such case remember that the first member of a struct and the struct itself shares the same memory address, you just need to dereference a pointer to the original type via cast:

Switch from:

struct arr const res_c99 = {tmp};
return res_c99;

to

return *(struct arr *)&tmp;
  • Related