Por que podemos usar arrays de arrays em C? Essa foi a minha pergunta quando abri a linguagem C padrão (C17).
Encontrei algo interessante (6.2.5 "Tipos" /20):
Qualquer número de tipos derivados pode ser construído a partir dos tipos objeto e função, como segue: — Um tipo array descreve um conjunto não vazio de objetos alocados contiguamente com um tipo de objeto membro específico, chamado de tipo elemento. O tipo elemento deve ser completo sempre que o tipo array for especificado. Tipos array são caracterizados por seu tipo elemento e pelo número de elementos no array. Um tipo array é dito ser derivado de seu tipo elemento, e se seu tipo elemento for T, o tipo array é algumas vezes chamado de "array de T". A construção de um tipo array a partir de um tipo elemento é chamada de "derivação de tipo array".
Em C, temos tipos de objeto, tipos de função e tipos incompletos. Os elementos de um array têm um tipo de objeto, não um tipo derivado, e pensei: "E agora?". E então encontrei isto (6.7.6.2/1 e 6.7.6.2/3):
Além dos qualificadores de tipo opcionais e da palavra-chave static, [ e ] podem delimitar uma expressão ou*. Se delimitarem uma expressão (que especifica o tamanho de um array), a expressão deverá ser do tipo inteiro. Se a expressão for uma expressão constante, deverá ter um valor maior que zero. O tipo de elemento não deverá ser um tipo incompleto ou de função. Os qualificadores de tipo opcionais e a palavra-chave static deverão aparecer apenas na declaração de um parâmetro de função com um tipo array e, mesmo assim, apenas na derivação de tipo de array mais externa.
- Se, na declaração “T D1”, D1 tiver uma das formas: D [type-qualifier-listopt assignment-expressionopt] D [type-qualifier-listopt assignment-expression] D [type-qualifier-list static assignment-expression] D [type-qualifier-listopt] e o tipo especificado para ident na declaração “TD” for “derived-declarator-type-list T”, então o tipo especificado para ident será “derived-declarator-type-list array of T”.144) (Consulte 6.7.6.3 para o significado dos qualificadores de tipo opcionais e da palavra-chave static.)
Em 6.7.6.2/1, entendi que o tipo de elemento não pode ser um tipo de função nem um tipo incompleto, mas depois entendi que um tipo de elemento pode ser um tipo de objeto e um tipo derivado. Em 6.7.6.2/3, se o tipo for derivado, podemos criar um array de tipo derivado. E, na minha opinião, array é um tipo derivado que pode ser classificado como tipo de objeto. Mas esta é apenas a minha opinião, não digo que isso seja verdade. Se eu estiver errado, por favor, explique o porquê.
Parece que você foi enganado por esta linha:
o que faz parecer que os tipos derivados são uma categoria separada de tipos de objetos e funções.
Não são. Todos os tipos são tipos de objeto ou tipos de função. Ser um tipo derivado não impede que um tipo seja um tipo de objeto ou função.
Todos os tipos de array são tipos de objeto, então é perfeitamente aceitável ter um tipo de array cujo tipo de elemento seja outro tipo de array.
Do Projeto de Norma C23 n3220 6.2.5 1:
"Particionado" aqui significa que existem exatamente duas categorias distintas de tipos: tipos de objeto e tipos de função. C23 é citado acima, mas linguagem idêntica pode ser encontrada nos Padrões C17 e C11. Os Padrões C99 e C89 anteriores tinham linguagem semelhante, mas não idêntica, que também incluía tipos incompletos neste esquema de particionamento. Para C11 e posteriores, existem apenas tipos de objeto e tipos de função, com tipos de objeto subdivididos em tipos de objeto completos e incompletos.
Matrizes não são funções, são objetos. É aceitável que uma matriz tenha membros do tipo matriz (completos), pois matrizes são tipos de objetos.
Em C, há objetos e funções que podem ter tipos diferentes.
Do Padrão C (6.2.5 Tipos, p.#1):
O tipo array nada mais é do que um tipo de objeto derivado de outros tipos de objeto. Ou seja (o Padrão C, Tipos 6.2.5):
Por exemplo, de forma semelhante aos tipos de array, você pode construir diferentes tipos de ponteiros:
Se o compilador suportar o padrão C23, você poderá escrever
Ou seja, você pode criar novos tipos de objetos a partir de tipos de objetos já existentes.