Я заметил, что компиляторы C (gcc, clang, tinycc) позволяют мне без предупреждения назначать указатель на больший массив указателю на меньший VLA:
#include <stdio.h>
#if !__TINYC__
void take_vla(int N, char const X[1][N]) { printf("%zd\n", sizeof(*X)); }
#endif
int main()
{
static char bigarray[] = "0123456789abcdefghijklmnopqrstuvwxyz";
//VLA
int n = 3;
char const (*subarray2)[n]=&bigarray;
//char const (*subarray3)[(int){3}]=&bigarray; //VLA but clang doesn't see it as such (a bug, I guess)
#if !__TINYC__
take_vla(3,&bigarray);
take_vla(3,&"abcdefg");
#endif
#if 0
char const (*subarray1)[3]=&bigarray; //-Wincompatible-pointer-types
#endif
}
Это соответствует C и почему?
В вашем объявлении subarray2n используется только для вычислений адресов и не приводит к выделению памяти, например в subarray2++ будет добавлен размер символов n. Можно считать, что компилятор, когда это возможно, может выдать предупреждение, когда назначенный указатель указывает на объект, размер которого не совпадает с размером n, однако это будет проблемой времени выполнения, а не проблемой времени компиляции.
@Skizz Да, но с системой типов C никогда не бывает так просто, не так ли. Попробуйте изменить последний #if 0 на #if 1, и вы получите предупреждение об этом назначении (более крупный простой старый массив на меньший простой старый массив). Не имеет значения, если «указатели - это просто числа», но для компилятора это каким-то образом имеет значение.
может быть, потому что мог - это предупреждения или массивы проверки границ, что вы обманываете с этим?
@PaulOgilvie Это обоснованное соображение, что динамический int n может быть неизвестен статически (например, он может исходить из аргумента функции), и тогда компилятор не может проверить, но мне интересно, могут ли когда-либо быть какие-либо проблемы даже с не -vla версия. char (*p)[n] = &(char [N/*N>n*/]){0}; /*... use p*/ не выглядит совместимым в соответствии со строгими правилами псевдонима, но подмассивы являются подобъектами более крупных массивов, и вы никогда не сможете получить к ним доступ целиком (только к отдельным элементам), поэтому, возможно, char (*p)[n] = (void*)&(char [N/*N>n*/]){0}; /*... use p*/ никогда не должен приводить к каким-либо проблемам.
@PSkocik: если я не ошибаюсь (и это всегда возможно!), Этот код в последнем #if включает в себя назначение между двумя разными типами: массив указателя на char и указатель на указатель на char, поэтому компилятор может легко обнаружить это и выдать предупреждения.
@Skizz: В C указатели представляют собой нет просто числа для ЦП, потому что они имеют информацию о типе (которая влияет на арифметику указателей в C), и потому что они абстракции.
Код не строго соответствует C, потому что ограничения для операторов присваивания (C 2018 6.5.16.1 1) говорят, что два указателя должны быть указателями на версии совместимых типов, и правила, которые могут сделать указатели на два типа массивов совместимыми (6.2.7 3) не предусмотрены для массивов разной заданной длины. («Указанная» в этом случае включает длину, определенную путем вычисления выражения размера.)
@EricPostpischil Я думаю, что даже для массивов, не относящихся к VL, int (*p)[3]=(char (*)[3])&(int[4]){0}; /*... use p in any way without casting*/ никогда не должен приводить к каким-либо строгим проблемам с псевдонимом, потому что, хотя указанные типы несовместимы, для нарушения строгих правил псевдонима вам придется получить доступ к объекту через L -значение несовместимо с его эффективным типом, но в C. нет такой вещи, как L-значения, типизированные для массивов. Массивы всегда будут распадаться до указателя на int, а int действительно там. Что вы думаете?
@EricPostpischil: Я говорил о коде, который ЦП видит во время выполнения, ЦП не знает, на каком языке был создан код, поэтому это просто числа. В языке C только компилятор (и с некоторыми метаданными - отладчик) связывает тип с адресом. Эта ссылка потеряна в конечном исполняемом файле. Некоторые языки (например, Java, C#) поддерживают эту связь между типом и адресом, поэтому возможны проверки типов во время выполнения. Арифметика указателя - это то, что компилятор генерирует на основе информации о типе, а для ЦП он просто складывает два числа вместе.
@Skizz: (а) Если вы говорили о том, что говорит ЦП, то это не «в C.» (б) Это верно не для всех процессоров. Некоторые процессоры используют архитектуру сегментированных адресов, поэтому адреса - это не просто числа. Есть и другие приукрашивания и вариации схем адресации.
@Skizz проблема с типами псевдонимов заключается в том, что он позволяет компилятору принять restrict и на его основе сгенерировать другой код. Если вы выполните int f(int *X, short *Y){ *X=2; *Y=3; return *X; }, он может сгенерировать код, который возвращает 2 в качестве промежуточного, потому что, поскольку X и Y не могут быть псевдонимами, * Y = 3 не может изменить X. На первый взгляд похоже, что то же самое может произойти с назначением подмассива / литье, но я не думаю, что это может быть основано на букве стандарта, потому что стандарт определяет строгий псевдоним с точки зрения доступа к lvalue, массивы lvalue не существуют из-за распада массива-2-указателя
@EricPostpischil: Ах, я вижу, где произошла путаница, извините. Да уж, люди, которые читают слишком много стандартов, так разборчивы в грамматике! (Шутить)





const char[3] несовместим с char[37].
Также «указатель на квалифицированный тип» несовместим с «указателем на тип» - не путайте это с «квалифицированным указателем на тип». (К сожалению, корректность констант не работает с указателями на массивы.)
Соответствующей частью является правило простого присвоения C17 6.5.16.1:
- the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
Смотрим на разные компиляторы:
gcc в "режиме GNU" бесполезен для проверки соответствия C. Вы должны компилировать с -std=cxx -pedantic-errors. После чего gcc работает нормально: gcc -std=c17 -pedantic-errors:
error: pointers to arrays with different qualifiers are incompatible in ISO C [-Wpedantic]
icc дает ту же диагностику, что и gcc, работает нормально.
В то время как clang -std=c17 -pedantic-errors не сообщает об ошибках, он явно не соответствует стандарту C.
В C указатели - это просто числа для ЦП, нет дополнительной информации о том, на что они указывают, и язык также не определяет никаких метаданных, поэтому не имеет значения размер того, на что он указывает. , просто тип тот же (и дело в компиляторе, а не в процессоре).