Весь диспут начался с поста DarkCoder

// OK
int a[4];
sizeof(a);

// OK
void foo(int a[4]){sizeof(a);}

// FAIL - будет размер указателя
void foo(int* a){sizeof(a);}

где автор ошибочно считал, что в случаях 1 и 2 результат одинаков, и ты справедливо указал на ошибку. Хотя операнд операции sizeof чисто внешне декларирован одинаково: 'int [4]', т.е. непосредственный контекст (sizeof) здесь ни при чем, здесь существенно то, что во 2 случае 'int a[4]' - это не декларация массива, а декларация указателя в "особо извращенной форме", и это не махинации конкретного компилятора, это определение языка, ну не
может формальный параметр функции реально (т.е. по реализации в объектном коде)
иметь тип "int [4]", не передаются массивы by value. И все дальнейшее про 'decaying' можно было не оглашать. А уж если оглашать, то опять же с ремаркой о том, что, во всех случаях, кроме вышеозначенного, приведение 'type [n]' к 'type *' - чисто умозрительное, и только для массива - параметра функции в точке вызова создается в стеке ячейка-указатель, в нее кладется адрес фактического массива (хотя это может результат любого выражения с типом "ссылка на"), а в объектном коде самой функции
этот "массив" во всех контекстах (в том числе в sizeof) юзается точно так же, как указатель-параметр или указатель-auto-переменная, т.е. как и любой другой указатель это переменная периода исполнения, тогда как имя настоящего массива на этапе исполнения - это константный виртуальный адрес (static) или константное смещение относительно SP (auto). Ну а дополнение по поводу VLA вынужден был сделать, потому что при их наличии смещение относительно SP может быть и не константным.

Короче, еще раз - разница в результатах 1 и 2 в том, что во втором случае 'a' - это указатель, не массив, "приведенный к указателю", а просто указатель, физически. Точка.
Перечитал, что написал, и понял, что опять немного погорячиллся ("имя массива в любом контексте имеет тип "ссылка на" свой элемент"): в контексте операнда sizeof и унарной '&' имя массива действительно сохраняет свой изначально декларированный тип 'type [n]' - иначе все остальное про sizeof теряет смысл.
Давай различать указатель-тип и указатель-переменную, и, чтобы не запутаться,
называть тип "ссылка на ..." вместо "указатель на ...".


int a[10];
int *a2 = a;

void func(int *arg_p, int arg_a[10]) {
int *la;
la = a;
la = a2;
la = arg_p;
la = arg_a;
}


Здесь все переменные имеют тип "ссылка на int", то бишь 'int *'.
А вот результат трансляции в ассемблерный код:


5:ptr.c **** int *la;
6:ptr.c **** la = a;
27 .loc 1 6 0
28 000c 48C745F8 movq $a, -8(%rbp)
28 00000000
7:ptr.c **** la = a2;
29 .loc 1 7 0
30 0014 488B0500 movq a2(%rip), %rax
30 000000
31 001b 488945F8 movq %rax, -8(%rbp)
8:ptr.c **** la = arg_p;
32 .loc 1 8 0
33 001f 488B45E8 movq -24(%rbp), %rax
34 0023 488945F8 movq %rax, -8(%rbp)
9:ptr.c **** la = arg_a;
35 .loc 1 9 0
36 0027 488B45E0 movq -32(%rbp), %rax
37 002b 488945F8 movq %rax, -8(%rbp)


Отчетливо видно, что присвоение переменой 'la' имени внешнего массива 'a' отличается
от присвоения внешнего указателя 'a2' - в первом случае это непосредственный операнд
'$a' - именно адрес первого элемента массива a, во втором это содержимое
32-разрядного (или 64-разрядного) слова - указателя, размещенного по адресу '$a2',
т.е. в первом случае происходит одно разыменование, во втором - два.
Так же отчетливо видно, что присвоения 'la' указателя 'arg_p' и "массива" 'arg_a' ничем
не отличаеются - во обоих случаях происходит двойное разыменование. Кажется, Ритчи
напрасно допустил это послабление в языке, ведь ясно же было сказано
изначально, что массивы в C не передаются by value - чтобы не провоцировать
на написание неэффективных программ, но все же - (вот оно - послабление)
формальный параметр можно описать как массив - для удобства, а размерность
можно и не указывать - она все равно будет проигнорирована, ведь реально это указатель.
Кому-то это реально заплело мозги, я встречал программистов, которые на 5-ом году
активного использования C линковали 2 объектника, в исходниках одного
из которых писали 'int a[10]',
а в исходниках другого 'extern int *a', и потом жаловались на 'Memory fault' или
'Segmentation violation' - мол, в компиляторе ошибка.

Так вот, еще раз: никто ничего не "схлопывает",
имя массива в любом контексте имеет тип "ссылка на" свой
элемент, но приведение массива к указателю как к объекту данных (не к типу,
а к объекту данных на уровне объектного кода!) происходит только в одном контексте -
когда формальный параметр функции описан как массив, а все потому, что фактический
параметр - это реально засунутый в стек в точке вызова 32-битный (или 64-битный)
указатель, с которым в объектном коде функции надо и обращаться как с указателем - т.е.
двойным разыменованием. Именно поэтому sizeof от
массива-формального параметра всегда будет возвращать sizeof указателя (т.е. 4 или 8
в зав-сти от арх-ры), тогда как sizeof от любого другого массива будет возвращать
именно размер массива или error в случае incomplete type.

Что касается VLA, то просто откомпилируй и выполни тот код, там длина 'n' массива
'double d[n]' задается аргументом командной строки, какое там может быть
"уже посчитанное для инициализации массива значение длины" ? Там sizeof реально
вычисляется на этапе выполнения, и при запусках с разным значением аргумента
выводятся разные значения sizeof(s), и размер фрейма стека, выделяемого
активации функции f1, вычисляется непосредственно уже в объектном коде самой функции.
виноват, в предыдущем посте, конечно gcc, а не gss а include подключают stdio.h и stdlib.h. Кстати - как избежать здесь интерпретации спец. HTML символов, неужели писать \< \>
что-то вы заплели простой вопрос. Массив в C трактуется как указатель только в одном контексте - в качестве формального параметра функции. На самом деле это и есть указатель на первый элемент фактического массива, а возможность описать его в функции как массив - чисто для удобства и выразительности. В любом другом контексте (static, auto, extern, компонент struct) массив - это именно массив, и sizeof учитывает его размерность, как бы она ни задавась - явно или через длину инициатора (если размерность никак не задана, например в extern, то это incomplete type, и применение sizeof некорректно). Более того, начиная с C99 допускаются variable length arrays, и sizeof в общем случае вычисляется уже на этапе выполнения.
Вот пример (компилируется gss -std=c99 ...):

#include
#include

static float farr[23];
typedef long la[80];
extern double exarr[];
extern double exarr_def[4];


static void f1(int n, short lfarr[77]) {
struct {
double d[n];
int n;
} s;
printf("In f1(): sz(%u)=%u\n", n, sizeof(s)); // n * sizeof(double) + sizeof(int)
// >= n * 8 + 4
printf("In f1(): sz(lfarr)=%u\n", sizeof(lfarr)); // sizeof(short *) == 4 || 8
}

int main(int ac, char **av) {
short lfarr[77];
static long long ll[] = {1, 2, 3};
if (ac != 2) {
fprintf(stderr, "Usage: %s num\n", av[0]);
exit(1);
}
f1(atoi(av[1]), lfarr);
printf("sz(farr)=%u\n", sizeof(farr)); // 23 * sizeof(float) == 92
printf("sz(lfarr)=%u\n", sizeof(lfarr)); // 77 * sizeof(short) == 154
printf("sz(char [20]))=%u\n", sizeof(char [20])); // 20 * sizeof(char) == 20
printf("sz(long)=%u\n", sizeof(long)); // sizeof(long) == 8
printf("sz(la)=%u\n", sizeof(la)); // 80 * sizeof(long) == 640
printf("sz(long long)=%u\n", sizeof(long long)); // sizeof(long long) == 8
printf("sz(ll)=%u\n", sizeof(ll)); // 3 * sizeof(long long) == 24
// printf("sz(exarr)=%u\n", sizeof(exarr)); // unknown
printf("sz(exarr_def)=%u\n", sizeof(exarr_def)); // 4 * sizeof(double) == 32
}