Указание указателя на буфер памяти на указатель на VLA

в C, я считаю, что следующая программа верна: наведение указателя на выделенный буфер памяти на массив, подобный этому:

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

#define ARRSIZE 4

int *getPointer(int num){
    return malloc(sizeof(int) * num);
}

int main(){
    int *pointer = getPointer(ARRSIZE);
    int (*arrPointer)[ARRSIZE] = (int(*)[ARRSIZE])pointer;

    printf("%d\n", sizeof(*arrPointer) / sizeof((*arrPointer)[0]));

    return 0;
}

(выводится 4).

Однако, безопасно ли в C99 делать это с помощью VLA?

    int arrSize = 4;
    int *pointer = getPointer(arrSize);
    int (*arrPointer)[arrSize] = (int(*)[arrSize])pointer;

    printf("%d\n", sizeof(*arrPointer) / sizeof((*arrPointer)[0]));

    return 0;

(также выводит 4).

Является ли это законным, в соответствии со стандартом C99?

Было бы довольно странно, если бы это было законно, поскольку это означало бы, что VLA эффективно разрешают создание динамического типа, например, типы типа type(*)[variable].

Ответ 1

Да, это законно, и да, измененная система типов чрезвычайно полезна. Вы можете использовать синтаксис натурального массива для доступа к смежному 2-мерному массиву, размеры которого неизвестны до времени выполнения.

Его можно назвать синтаксическим сахаром, так как вы ничего не можете сделать с этими типами, с которыми вы не могли бы обойтись без них, но это делает чистый код (на мой взгляд).

Ответ 2

Я бы сказал, что это действительно так. Окончательная версия стандарта C99 (цитируется Wikipedia) говорится в параграфе 7.5.2 - деклараторы массивов alinea 5: Если размер является выражением, которое не является целочисленным постоянным выражением:... каждый раз, когда он оценивается, он должен иметь значение больше нуля. Размер каждого экземпляра типа массива переменной длины не изменяется в течение его срока службы.

Он даже прямо говорит, что его можно использовать в sizeof при условии, что размер никогда не изменяется: где размер выражение является частью операнда оператора sizeof и изменяет значение выражение размера не повлияет на результат оператора, не определено, независимо от того, выражение размера оценивается.

Но стандарт также говорит, что это разрешено только в деклараторе области блока или прототипе функции: обычный идентификатор (как определено в 6.2.3), который имеет измененный тип, должен иметь либо область видимости блока, либо отсутствие связей или возможностей прототипа функции. Если объявлен идентификатор чтобы быть объектом со статической продолжительностью хранения, он не должен иметь тип массива переменной длины.

И пример позже объясняет, что он не может использоваться для полей-членов, даже в области блоков:

...
void fvla(int m, int C[m][m]); // valid: VLA with prototype scope
void fvla(int m, int C[m][m]) // valid: adjusted to auto pointer to VLA
{
    typedef int VLA[m][m]; // valid: block scope typedef VLA
    struct tag {
        int (*y)[n]; // invalid: y not ordinary identifier
        int z[n]; // invalid: z not ordinary identifier
    };
...