3

I've written a function in C to get the size of the terminal:

int get_terminal_size()
{
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

    return w.ws_row * w.ws_col;
}

However, I want to use it as the size parameter when initialising an array in a struct:

struct level
{
    [...]
    char level_data[get_terminal_size()];
}

However, this doesn't compile, as the compiler tells me that the initialiser element needs to be constant (which makes sense, but assigning the return value to a const int doesn't work either).

Is there any other way of doing what I need to do in C?

Thanks in advance.

emberdex
  • 355
  • 1
  • 7
  • 1
    Other than dynamically allocating the memory, you could see if you can have `level_data` be a flexible array in the struct https://stackoverflow.com/questions/8687671/how-to-initialize-a-structure-with-flexible-array-member – jackarms Aug 16 '17 at 23:42
  • @jackarms-- dynamic allocation is still needed with a FAM. – ad absurdum Aug 16 '17 at 23:49
  • `const` doesn't mean "constant". A constant expression is one that can be evaluated at compile time. The `const` keyword should perhaps have been spelled `readonly`, because that's what it really means. This valid declaration: `const int r = rand();` demonstrates the difference (note that it's valid only at block scope, not at file scope). – Keith Thompson Aug 16 '17 at 23:49
  • Are you using C99 or C11 with VLA availablilty? – chux - Reinstate Monica Aug 17 '17 at 04:15

3 Answers3

2

The size of the array has to be known at compilation time. If you want to allocate memory dynamically you have to use malloc.

char *level_data = malloc(get_terminal_size());
if (level_data == NULL)
{ /* handle allocation failure */ }
/* ...Do something with your array... */
free(level_data);

People in comments, thank you for the corrections.

ChrisB
  • 498
  • 3
  • 10
2

A fixed length array must be declared with a positive integer constant value in the brackets so that its size may be known at compile-time; note that a const int is not a compile-time constant, but instead a read-only value. C has had variable length arrays since C99, which can be sized at runtime with variables instead of (compile-time) constants. If the posted code was compiled under C99, then this was interpreted as a VLA:

char level_data[get_terminal_size()];

VLAs were made optional in C11, but they appear to be well-supported in most C11 implementations. Under C89, the above declaration would raise an error since there is no support for VLAs here. But in more modern C implementations that support VLAs, the problem is that a struct can not contain a VLA member.

With Dynamic Allocation

To obtain an array member in a struct that has its length determined at runtime, you could use either a pointer to dynamically allocated memory, or a flexible array member. A flexible array member is an incomplete array type declared as the last member of a struct with more than one named member. The size of such a struct is calculated as if the FAM were omitted:

struct level {
    int id;
    size_t data_sz;
    char level_data[];
};

To use a FAM, you will need to allocate memory for the struct plus memory for the FAM. You may wish to store the size of the FAM in the struct, and you will still need to remember to free the allocation when you are finished with it. Here is an example:

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

#define DATA_ARRAY_SZ  10;

struct level {
    int id;
    size_t data_sz;
    char level_data[];
};

size_t get_size(void);

int main(void)
{
    size_t size = get_size();
    struct level *my_level;
    my_level = malloc(sizeof *my_level
                      + sizeof *my_level->level_data * size);
    if (my_level == NULL) {
        perror("Unable to allocate memory");
        exit(EXIT_FAILURE);
    }

    my_level->data_sz = size;
    for (size_t i = 0; i < my_level->data_sz; i++) {
        my_level->level_data[i] = 'a' + i;
    }

    for (size_t i = 0; i < my_level->data_sz; i++) {
        printf("%c", my_level->level_data[i]);
    }
    putchar('\n');

    /* Free allocated memory */
    free(my_level);

    return 0;
}

size_t get_size(void)
{
    return DATA_ARRAY_SZ;
}

Without Dynamic Allocation

If VLAs are available, there is a simpler option that does not require dynamic allocation. You could declare level_data as a pointer to char, and declare a VLA of the needed size at runtime, assigning the address of the first element of the VLA to level_data. Since there is no dynamic allocation, there is nothing to deallocate later. This approach also does not suffer from a limitation of structures with flexible array members: they can't be used as members of other structs or arrays.

#include <stdio.h>

#define DATA_ARRAY_SZ  10;

struct level {
    int id;
    size_t data_sz;
    char *level_data;
};

size_t get_size(void);

int main(void)
{
    size_t size = get_size();
    char data_arr[size];

    struct level my_level = { .data_sz = size, .level_data = data_arr };

    for (size_t i = 0; i < my_level.data_sz; i++) {
        my_level.level_data[i] = 'a' + i;
    }

    for (size_t i = 0; i < my_level.data_sz; i++) {
        printf("%c", my_level.level_data[i]);
    }
    putchar('\n');

    return 0;
}

size_t get_size(void)
{
    return DATA_ARRAY_SZ;
}
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • 1
    Thank you very much for the detailed answer! Definitely learned something new about flexible array members, but I'll go the char pointer route as it seems relatively easy to implement (especially because I'm loading in level data from files and can just strcpy it in) – emberdex Aug 17 '17 at 08:13
-2

ChrisB is correct, you cannot set the length of an array dynamically in C. I think the reason for this "quirk" is important to understand when coming from other languages.

Every value and variable in C needs a spot in memory, and each program is allocated a chunk of memory to store its values. This is divided into the stack, and the heap.

The heap is a big chunk of memory reserved for later allocation. When you run malloc, what you get back is a pointer to a small section of the heap you can use as you'd like.

The stack is where we store memory that functions need to do their work. If your program looks like

void my_function(void) {
    int a = 1;
    return;
}
int main(void) {
    int x = 0; // (A)
    x++;
    my_function(); // (B)
    x++;
    return 0;
}

When we're at (A), the stack reserves an int-sized spot to store x, as it's played around with. When we're at (B), we still need to hold the space for x in memory, but we also need space for a now.

The "stack" is packed tightly to make use of the available space, and to pack it tightly, the compiler needs to know exactly how much space on the stack each function needs for its local variables. The heap is less tightly packed, and so doesn't have the same requirement.

Jeremy
  • 104
  • 3
  • 12
  • 1
    C has had VLAs since C99; the problem here is that a `struct` can't have a VLA member. – ad absurdum Aug 17 '17 at 00:42
  • Shoot, I actually hadn't realized that was supported. Thanks for the heads up, I have something exciting to learn today. – Jeremy Aug 17 '17 at 12:53
  • 1
    Introduced in C99, then made optional in C11 (but commonly available). There are some disadvantages to VLAs, too: they are typically stack-allocated, so limited in size; when it fails, allocation fails silently, whereas with `malloc()` you can check the return value for `NULL`; VLAs can't be defined at file-scope. They are particularly handy for dynamic allocation of true multi-dimensional arrays; you might see [this answer](https://stackoverflow.com/a/42094467/6879826) for details. – ad absurdum Aug 17 '17 at 13:26