Кастинг double * для двойного (*) [N]

void compute(int rows, int columns, double *data) {
    double (*data2D)[columns] = (double (*)[columns]) data;
    // do something with data2D
}

int main(void) {
    double data[25] = {0};
    compute(5, 5, data);
}

Иногда было бы очень удобно рассматривать параметр как многомерный массив, но он должен быть объявлен как указатель на плоский массив. Безопасно ли использовать указатель для обработки его как многомерного массива, как это делает compute в приведенном выше примере? Я уверен, что макет памяти гарантированно работает правильно, но я не знаю, позволяет ли этот стандарт указывать указатели таким образом.

Разве это нарушает какие-либо строгие правила псевдонимов? Как насчет правил для арифметики указателя; поскольку данные "на самом деле" не являются double[5][5], разрешено ли выполнять арифметику указателей и индексирование на data2D, или это нарушает требование, чтобы арифметика указателя не отклонялась от границ соответствующего массива? Является ли data2D даже гарантированным указание на нужное место, или просто гарантировано, что мы можем отбросить его и восстановить data? Стандартные котировки будут высоко оценены.

Ответ 1

Я заранее извиняюсь за несколько неопределенный ответ, так как кто-то сказал, что эти правила в стандарте довольно трудно интерпретировать.

C11 6.3.2.3 говорит

Указатель на тип объекта может быть преобразован в указатель на другой тип объекта. Если результирующий указатель неверен выровненный для ссылочного типа, поведение undefined.

Таким образом, фактический эффект отличен, если оба указателя имеют одинаковое выравнивание.

А затем, обращаясь к фактическим данным через указатель, C11 6.5 дает вам стену тарабарского текста, касающуюся "сглаживания", что довольно сложно понять. Я попытаюсь привести то, что, по моему мнению, является единственными соответствующими частями для этого конкретного случая:

"Эффективный тип объекта для доступа к его сохраненному значению объявленный тип объекта, если он есть." /-/

"Объект должен иметь сохраненное значение, доступное только с помощью значения lvalue выражение, которое имеет один из следующих типов:

  • совместимый тип с эффективным типом объекта"

/-/

  • "совокупный или объединенный тип, который включает один из вышеупомянутых типов среди его Члены"

(Вышеупомянутое иногда называют "строгим правилам псевдонимов", который не является формальным термином языка C, а скорее термином, составленным компилятором исполнители.)

В этом случае эффективным типом объекта является массив из 25 удвоений. Вы пытаетесь передать его указателю массива в массив из 5 удвоений. Независимо от того, считается ли он типом, совместимым с эффективным типом, или как совокупность, включающая тип, я не уверен. Но я уверен, что это считается одним из двух действительных случаев.

Итак, насколько я вижу, этот код не нарушает 6.3.2.3 и 6.5. Я считаю, что код гарантированно работает нормально, и поведение должно быть четко определено.

Ответ 2

Самый безопасный способ в таких ситуациях состоит в том, чтобы сохранить элементы в плоском одномерном массиве, но писать методы доступа для чтения и записи из этого массива многомерным способом.

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

const int rowCount = 10;
const int columnCount = 10;

const int dataSize = rowCount*columnCount;
double data[dataSize];


void setValue( const int x, const int y, double value)
{
    if ( x>=0 && x<columnCount && y>=0 && y<rowCount) {
        data[x+y*columnCount] = value;
    }
}


double getValue( const int x, const int y )
{
    if ( x>=0 && x<columnCount && y>=0 && y<rowCount) {
        return data[x+y*columnCount];
    } else {
        return 0.0;
    }
}


int main()
{
    memset(data, 0, sizeof(double)*dataSize);
    // set a value
    setValue(5, 2, 12.0);
    // get a value
    double value = getValue(2, 7);

    return 0;
}

В примере используются глобальные переменные, которые используются только для простоты. Вы можете передать массив данных в качестве дополнительного параметра в функции или даже создать контекст для работы.

В С++ вы переносите контейнер данных в класс и используете два метода в качестве методов доступа.