Странное предупреждение в аргументе многомерного массива const функции C - PullRequest
7 голосов
/ 01 января 2011

Я получаю некоторые странные предупреждения об этом коде:

typedef double mat4[4][4];

void mprod4(mat4 r, const mat4 a, const mat4 b)
{
/* yes, function is empty */
}

int main()
{
    mat4 mr, ma, mb;
    mprod4(mr, ma, mb);
}

gcc выводится следующим образом:

$ gcc -o test test.c
test.c: In function 'main':
test.c:13: warning: passing argument 2 of 'mprod4' from incompatible pointer
type
test.c:4: note: expected 'const double (*)[4]' but argument is of type 'double
(*)[4]'
test.c:13: warning: passing argument 3 of 'mprod4' from incompatible pointer
type
test.c:4:
note: expected 'const double (*)[4]' but argument is of type 'double
(*)[4]'

Если я определю функцию как:

void mprod4(mat4 r, mat4 a, mat4 b)
{
}

Или определение матриц в main как:

mat4 mr;
const mat4 ma;
const mat4 mb;

Или вызвать функцию main как:

mprod4(mr, (const double(*)[4])ma, (const double(*)[4])mb);

Или даже определение mat4 как:

typedef double mat4[16];

Удаляет предупреждение. Что здесь происходит? Я делаю что-то недействительное?

Версия gcc - 4.4.3, если применимо.

Я также написал на gcc bugzilla: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=47143

Мой нынешний обходной путь - создание некрасивых макросов, которые приводят меня в действие:

#ifndef _NO_UGLY_MATRIX_MACROS

#define mprod4(r, a, b) mprod4(r, (const double(*)[4])a, (const double(*)[4])b)

#endif

Ответ Джозефа С. Майерса о gcc bugzilla:

Не ошибка. Параметры функции имеют тип "указатель на массив [4] из const double "потому что const на тип массива применяется к элементу типа, рекурсивно, а затем только внешний тип массива параметр типа массива распадается на указатель, и переданные аргументы типа "указатель на массив [4] из double "после затухания массива в указатель, и единственный случай, когда квалификаторы разрешено добавлять в назначение, передача аргументов и т. д. является классификатором на цель непосредственного указателя, а не те, которые вложены глубже.

Звучит довольно странно для меня, как будто функция ожидает:

pointer to array[4] of const doubles

и мы проходим

pointer to const array[4] of doubles

Intead.

Или это будет обратное? Предупреждения предполагают, что функция ожидает:

const double (*)[4]

, который мне кажется больше похожим на

pointer to const array[4] of doubles

Я действительно смущен этим ответом. Может ли кто-то, кто понимает, что он сказал, уточнить и привести пример?

Ответы [ 6 ]

12 голосов
/ 01 января 2011

Я считаю, что проблема заключается в ограничениях, указанных в C99 6.5.16.1 (1) , которые, по-видимому, запрещают смешивать квалификации в заданиях, за исключением указателей, для которых включительно-квалификатор исключение определено. Проблема в том, что с косвенными указателями вы в конечном итоге передаете указатель на одну вещь на указатель на другую. Присвоение недопустимо, потому что, если бы оно было, вы могли бы обмануть его, модифицировав объект с квалификацией const, с помощью следующего кода:

const char **cpp;
char *p;
const char c = 'A';
cpp = &p;  // constraint violation
*cpp = &c; // valid
*p = 0;    // valid by itself, but would clobber c

Может показаться разумным, что cpp, который обещает не изменять никакие char с, может быть назначен указатель на объект, указывающий на неквалифицированные char с. В конце концов, это разрешено для одно-косвенных указателей, поэтому, например, вы можете передать изменяемый объект второму параметру strcpy(3), первому параметру strchr(3) и многим другим параметрам, которые объявлены с const.

Но с помощью косвенного указателя на следующем уровне допускается присвоение из квалифицированного указателя, и теперь совершенно безусловное присвоение указателя приведет к засорению квалифицированного объекта.

Я не сразу вижу, как двумерный массив может привести к этой ситуации, но в любом случае он соответствует тому же ограничению в стандарте.

Поскольку в вашем случае вы на самом деле не обманываете его, чтобы заглушить константу, правильная вещь для вашего кода, похоже, вставляет приведение.


Обновление: Хорошо, ребята, как это происходит, эта проблема в C faq , и все это обсуждение также происходило несколько раз в списке ошибок gcc и в списке рассылки gcc.

Урок: вы можете передать T *x, когда ожидается const T *x, за явным исключением, но T *x и const T *x по-прежнему являются различными типами, поэтому вы не можете передать указатель на один из них указатель на другой.

6 голосов
/ 01 января 2011

Чтобы объяснить, что сказал Джозеф: функция ожидает, что pointer to array[4] of const double будет передано, но вы передаете pointer to array[4] of double.Эти типы не совместимы, поэтому вы получаете сообщение об ошибке.Они выглядят так, как будто они должны быть совместимыми, но это не так.

Для передачи параметров в функции (или для назначения переменных) вы всегда можете преобразовать X в const X илиот pointer to X до pointer to const X для любого типа X.Например:

int x1 = 0;
const int x2 = x1;  // ok
int *x3 = &x1;
const int *x4 = x3;  // ok: convert "pointer to int" to "pointer to const int"
int **x5 = &x3;
const int **x6 = x5;  // ERROR: see DigitalRoss's answer
int *const *x7 = x5;  // ok: convert "pointer to (pointer to int)" to
                      //             "pointer to const (pointer to int)"

Вам разрешено добавлять квалификаторы (то есть квалификаторы const, volatile и restrict) к уровню first указатели.Вы не можете добавить их к указателям более высокого уровня, потому что, как упомянул DigitalRoss , это позволит вам случайно нарушить правильность const.Это то, что Джозеф подразумевает под «единственным случаем, когда квалификаторы могут быть добавлены в присваивании, передаче аргументов и т. Д. - это классификаторы на непосредственном целевом указателе, а не те, которые вложены глубже».в ответ Джозефа вы не можете преобразовать pointer to array[4] of double в pointer to array[4] of const double, потому что нет типа X, такого, что вы конвертируете из pointer to X в pointer to const X.

Если вы попытаетесьиспользуя array[4] of double для X, вы увидите, что вы можете преобразовать в pointer to const array[4] of double, который является другим типом.Однако в C такого типа не существует: у вас может быть массив типа const, но не существует такого понятия, как const array.

Следовательно, нет способа полностью решить вашу проблему.Вам нужно будет либо добавить приведение ко всем вызовам функций (вручную, либо с помощью макроса или вспомогательной функции), переписать свои функции, чтобы они не принимали const параметров (плохо, так как они не позволяют передавать const).матрицы) или измените тип mat4 на одномерный массив или структуру, как user502515 предложил .

2 голосов
/ 01 января 2011

Чтобы практически решить эту проблему, можно использовать структуру, и исключается изменение double[4][4] в a-bit-unkward double (*)[4], и constness также работает интуитивно - при использовании того же объема памяти:

struct mat4 {
        double m[4][4];
};

void myfunc(struct mat4 *r, const struct mat4 *a, const struct mat4 *b)
{
}

int main(void)
{
        struct mat4 mr, ma, mb;
        myfunc(&mr, &ma, &mb);
}
1 голос
/ 01 января 2011

Я думаю, что в C99 вы можете сделать это, но я не уверен, что это поможет:

void mprod4(double mr[4][4], double ma[const 4][const 4], double mb[const 4][const 4])
{

}

У меня нет удобного компилятора C99, но я помню, что кое-что читал в спецификации C99относительно квалификаторов в пределах [] для массивов в качестве аргументов.Вы также можете поместить туда static (например, ma[static 4]), но, конечно, это означает что-то еще.

Редактировать

Вот оно, раздел 6.7.3.5, параграф 7.

Объявление параметра как «массива типа» должно быть скорректировано до «квалифицированного указателя на тип», где квалификаторы типа (если таковые имеются) - это те, которые указаны в [ и ]вывод типа массива.Если ключевое слово static также присутствует в [ и ] деривации типа массива, то при каждом вызове функции значение соответствующего фактического аргумента должно обеспечивать доступ к первому элементу массива с atнаименьшее количество элементов, указанное в выражении размера.

1 голос
/ 01 января 2011

Вот проблема (IMHO): double[4][4] в сигнатуре функции.

Вы знаете, что это double[4][4], но компилятор видит double(*)[4] в списке параметров функции, который, в частности, не имеет ограничений по размеру массива.Он превращает ваш 2D-массив из 4 на 4 объекта в указатель на одномерный массив из 4 объектов, и указатель может быть правильно проиндексирован, как если бы это был массив из 4 объектов.

Я бы передал все mat4 объекты по указателю:

void mprod4(mat4 *r, const mat4 *a, const mat4 *b);
// and, if you don't want to hairy your syntax
#define mprod4(r, a, b) (mprod4)(&r, (const mat4 *)&a, (const mat4 *)&b)

Это (я считаю) обеспечит правильность констант и правильность размера массива.Это может сделать mprod4 более трудным для написания, и все еще включает в себя некоторые волосатые приведения, но это (ИМХО) того стоит (особенно после макроса выше):

void mprod4(mat4 *r, const mat4 *a, const mat4 *b)
{
    // all indexing of the matricies must be done after dereference
    for(int i = 0; i < 4; i++) for(int j = 0; j < 4; j++)
      {
        (*r)[i][j] = (*a)[i][j] * (*b)[i][j];
        // you could make it easier on yourself if you like:
        #define idx(a, i, j) ((*a)[i][j])
        idx(r, i, j) = idx(a, i, j) * idx(b, i, j)
      }
}

Это может выглядетьнемного плохо, когда ты пишешь это, но я думаю, что это будет чище по типу.(Может быть, я слишком много думал о C ++ ...)

0 голосов
/ 01 января 2011

Компилятор просто анальный.

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

РЕДАКТИРОВАТЬ: похоже, что gcc не жалуется на другие преобразования const в const, например прохождение char * там, где ожидается const char *. В этом случае я склонен согласиться с тем, что Джозеф Майерс из Бугзиллы прав.

...