Por que sizeof (my_arr) [0] compila e tamanho igual (my_arr [0])?

129

Por que esse código é compilado?

_Static uint32_t my_arr[2];
_Static_assert(sizeof(my_arr) == 8, "");
_Static_assert(sizeof(my_arr[0]) == 4, "");
_Static_assert(sizeof(my_arr)[0] == 4, "");

As duas primeiras afirmações estão obviamente corretas, mas eu esperava que a última linha falhasse, pois meu entendimento é que isso sizeof()deve ser avaliado como um literal inteiro, que não pode ser tratado como uma matriz. Em outras palavras, falharia da mesma maneira que a seguinte linha falha:

_Static_assert(4[0] == 4, "");

Curiosamente, o seguinte não compila (o que deveria estar fazendo a mesma coisa, não?):

_Static_assert(*sizeof(my_arr) == 4, "");

erro: argumento de tipo inválido de unary '*' (tem 'long unsigned int') _Static_assert (* sizeof (my_arr) == 4, "");

Se isso importa, estou usando o gcc 5.3.0

Bgomberg
fonte
15
Eu suspeito que ( sizeof( my_arr ) )[ 0 ]falha.
Andrew Henle
Uma duplicata recente apresenta outra variação dessa surpresa de sintaxe: Por que o sizeof (x) ++ é compilado?
Peter Cordes

Respostas:

197

sizeofnão é uma função. É um operador unário como !ou ~.

sizeof(my_arr)[0]analisa como sizeof (my_arr)[0], que é apenas sizeof my_arr[0]com parênteses redundantes.

Isto é como !(my_arr)[0]analisa como !(my_arr[0]).

Em geral, os operadores de postfix têm precedência mais alta que os operadores de prefixo em C. sizeof *a[i]++analisa como sizeof (*((a[i])++))(os operadores de postfix []e ++são aplicados aprimeiro, depois os operadores de prefixo *esizeof ).

(Esta é a versão da expressão sizeof. Há também uma versão de tipo, que recebe um nome de tipo entre parênteses:. sizeof (TYPE)Nesse caso, os parênteses seriam necessários e parte da sizeofsintaxe.)

melpomene
fonte
14
Eu definitivamente sabia que sizeof era um operador unário e não uma função, mas esqueci completamente. Woops. Obrigado pela explicação detalhada. O fato de [] ter precedência maior que * é interessante, independentemente.
bgomberg
@melpomene Interessante. Eu nunca pensei em sizeofser um operador unário.
mutantkeyboard
5
Você não quer dizer "... parses as sizeof (my_arr[0])"? Apenas adicionar um espaço não muda nada.
Bernhard Barker
Em sizeof((my_array)[0])vez disso, eu recomendaria
bolov 14/10
46

sizeofpossui duas "versões": sizeof(type name)e sizeof expression. O primeiro requer um par de ()argumentos. Mas o último - aquele com uma expressão como argumento - não tem em ()torno de seu argumento. Tudo ()o que você usa no argumento é visto como parte da expressão do argumento, não como parte da sizeofprópria sintaxe.

Como my_arré conhecido pelo compilador como um nome de objeto, não um nome de tipo, você sizeof(my_arr)[0]é realmente visto pelo compilador como sizeofaplicado a uma expressão:, sizeof (my_arr)[0]onde (my_arr)[0]está a expressão do argumento. O ()nome ao redor da matriz é puramente supérfluo. A expressão inteira é interpretada como sizeof my_arr[0]. Isso é equivalente ao seu anterior sizeof(my_arr[0]).

(Isso significa, aliás, que o seu anterior sizeof(my_arr[0])também contém um par de supérfluos ().)

É um equívoco bastante difundido que sizeofa sintaxe de alguma forma requer um par de ()argumentos. Esse equívoco é o que engana a intuição das pessoas ao interpretar expressões como sizeof(my_arr)[0].

Formiga
fonte
1
A primeira versão existe para que você possa verificar o tamanho de um número inteiro na máquina (em geral, quando não havia máquinas de 64 bits!), Mas intnão é uma expressão válida; portanto, não é possível usar o segunda forma com ele.
NH.
25

[]tem uma precedência maior que sizeof. Então sizeof(my_arr)[0]é o mesmo que sizeof((my_arr)[0]).

Aqui está um link para uma tabela de precedência.

mch
fonte
8

Você está usando a versão do sizeofoperador que aceita uma expressão como parâmetro. Ao contrário do que pega um tipo, ele não requer parênteses. Portanto, o operando é simples (my_arr)[0], com os parênteses sendo redundantes.

Quentin
fonte