Array declaration
Array es un tipo que consiste en una secuencia no vacía de objetos contiguamente asignados con un particular element type . El número de esos objetos (el tamaño del array) nunca cambia durante la vida útil del array.
Contenidos |
Sintaxis
En la gramática de declaraciones de una declaración de arreglo, la secuencia del type-specifier designa el element type (que debe ser un tipo de objeto completo), y el declarator tiene la forma:
[
static
(opcional)
calificadores
(opcional)
expresión
(opcional)
]
attr-spec-seq
(opcional)
|
(1) | ||||||||
[
calificadores
(opcional)
static
(opcional)
expresión
(opcional)
]
attr-spec-seq
(opcional)
|
(2) | ||||||||
[
calificadores
(opcional)
*
]
attr-spec-seq
(opcional)
|
(3) | ||||||||
| expression | - | cualquier expresión excepto el operador coma , designa el número de elementos en el arreglo |
| qualifiers | - |
cualquier combinación de los calificadores
const
,
restrict
, o
volatile
, solo permitido en listas de parámetros de función; esto califica el tipo de puntero al cual este parámetro de arreglo es transformado
|
| attr-spec-seq | - | (C23) lista opcional de atributos , aplicados al arreglo declarado |
float fa[11], *afp[17]; // fa es un array de 11 floats // afp es un array de 17 punteros a floats
Explicación
Existen varias variaciones de tipos de arreglo: arreglos de tamaño constante conocido, arreglos de longitud variable y arreglos de tamaño desconocido.
Arreglos de tamaño constante conocido
Si expresión en un declarador de array es una expresión constante entera con un valor mayor que cero y el tipo del elemento es un tipo con tamaño constante conocido (es decir, los elementos no son VLA) (desde C99) , entonces el declarador declara un array de tamaño constante conocido:
int n[10]; // las constantes enteras son expresiones constantes char o[sizeof(double)]; // sizeof es una expresión constante enum { MAX_SZ=100 }; int n[MAX_SZ]; // las constantes de enumeración son expresiones constantes
Los arreglos de tamaño constante conocido pueden usar inicializadores de arreglo para proporcionar sus valores iniciales:
int a[5] = {1,2,3}; // declara int[5] inicializado a 1,2,3,0,0 char str[] = "abc"; // declara char[4] inicializado a 'a','b','c','\0'
|
En las listas de parámetros de función, se permiten elementos de sintaxis adicionales dentro de los declaradores de array: la palabra clave
En cada
llamada a función
a una función donde un parámetro de array utiliza la palabra clave
void fadd(double a[static 10], const double b[static 10]) { for (int i = 0; i < 10; i++) { if (a[i] < 0.0) return; a[i] += b[i]; } } // una llamada a fadd puede realizar comprobación de límites en tiempo de compilación // y también permite optimizaciones como la precarga de 10 doubles int main(void) { double a[10] = {0}, b[20] = {0}; fadd(a, b); // CORRECTO double x[5] = {0}; fadd(x, b); // comportamiento indefinido: el argumento de array es demasiado pequeño } Si qualifiers están presentes, califican el tipo de puntero al cual se transforma el tipo de parámetro de array: int f(const int a[20]) { // en esta función, a tiene tipo const int* (puntero a const int) } int g(const int a[const 20]) { // en esta función, a tiene tipo const int* const (puntero const a const int) }
Esto se usa comúnmente con el
void fadd(double a[static restrict 10], const double b[static restrict 10]) { for (int i = 0; i < 10; i++) // el bucle puede desenrollarse y reordenarse { if (a[i] < 0.0) break; a[i] += b[i]; } } Arreglos de longitud variableSi expression no es una expresión constante entera , el declarador es para un arreglo de tamaño variable. Cada vez que el flujo de control pasa sobre la declaración, expression es evaluada (y siempre debe evaluarse a un valor mayor que cero), y el array es asignado (correspondientemente, lifetime de un VLA termina cuando la declaración sale del ámbito). El tamaño de cada instancia VLA no cambia durante su lifetime, pero en otra pasada sobre el mismo código, puede ser asignado con un tamaño diferente.
Ejecutar este código
#include <stdio.h> int main(void) { int n = 1; label:; int a[n]; // re-allocated 10 times, each with a different size printf("The array has %zu elements\n", sizeof a / sizeof *a); if (n++ < 10) goto label; // leaving the scope of a VLA ends its lifetime }
Si el tamaño es
Los arreglos de longitud variable y los tipos derivados de ellos (punteros a ellos, etc.) se conocen comúnmente como "tipos modificables variables" (VM). Los objetos de cualquier tipo modificable variable solo pueden declararse en el ámbito de bloque o en el ámbito del prototipo de función. extern int n; int A[n]; // Error: VLA en ámbito de archivo extern int (*p2)[n]; // Error: VM en ámbito de archivo int B[100]; // OK: arreglo en ámbito de archivo de tamaño constante conocido void fvla(int m, int C[m][m]); // OK: VLA en ámbito de prototipo VLA debe tener duración de almacenamiento automático o asignado. Los punteros a VLA, pero no los VLA mismos, también pueden tener duración de almacenamiento estático. Ningún tipo VM puede tener vinculación. void fvla(int m, int C[m][m]) // OK: puntero de duración automática/ámbito de bloque a VLA { typedef int VLA[m][m]; // OK: VLA de ámbito de bloque int D[m]; // OK: VLA de duración automática/ámbito de bloque // static int E[m]; // Error: VLA de duración estática // extern int F[m]; // Error: VLA con vinculación int (*s)[m]; // OK: VM de duración automática/ámbito de bloque s = malloc(m * sizeof(int)); // OK: s apunta a VLA en almacenamiento asignado // extern int (*r)[m]; // Error: VM con vinculación static int (*q)[m] = &B; // OK: VM de duración estática/ámbito de bloque } Los tipos modificables variablemente no pueden ser miembros de estructuras o uniones. struct tag { int z[n]; // Error: Miembro de estructura VLA int (*y)[n]; // Error: Miembro de estructura VM }; |
(desde C99) |
|
Si el compilador define la macro constante __STDC_NO_VLA__ como la constante entera 1 , entonces los tipos VLA y VM no están soportados. |
(desde C11)
(hasta C23) |
|
Si el compilador define la macro constante __STDC_NO_VLA__ como la constante entera 1 , entonces los objetos VLA con duración de almacenamiento automático no están soportados. El soporte para tipos VM y VLAs con duraciones de almacenamiento asignado es obligatorio. |
(desde C23) |
Arreglos de tamaño desconocido
Si la
expresión
en un declarador de array se omite, declara un array de tamaño desconocido. Excepto en listas de parámetros de función (donde tales arrays se transforman en punteros) y cuando está disponible un
inicializador
, dicho tipo es un
tipo incompleto
(nótese que un VLA de tamaño no especificado, declarado con
*
como tamaño, es un tipo completo)
(desde C99)
:
extern int x[]; // el tipo de x es "array de límite desconocido de int" int a[] = {1,2,3}; // el tipo de a es "array de 3 int"
|
Dentro de una struct , un array de tamaño desconocido puede aparecer como el último miembro (siempre que haya al menos otro miembro nombrado), en cuyo caso es un caso especial conocido como flexible array member . Consulte struct para más detalles: struct s { int n; double d[]; }; // s.d is a flexible array member struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // as if d was double d[8]
|
(desde C99) |
Calificadores
|
Si un tipo de array se declara con un calificador
|
(hasta C23) |
|
Un tipo de array y su tipo de elemento siempre se consideran idénticamente calificados, excepto que un tipo de array nunca se considera calificado como
|
(desde C23) |
typedef int A[2][3]; const A a = {{4, 5, 6}, {7, 8, 9}}; // array de array de const int int* pi = a[0]; // Error: a[0] tiene tipo const int* void* unqual_ptr = a; // OK hasta C23; error desde C23 // Notas: clang aplica la regla en C++/C23 incluso en modos C89-C17
|
typedef int A[2]; // _Atomic A a0 = {0}; // Error // _Atomic(A) a1 = {0}; // Error _Atomic int a2[2] = {0}; // OK _Atomic(int) a3[2] = {0}; // OK |
(desde C11) |
Asignación
Los objetos de tipo array no son lvalues modificables , y aunque se puede tomar su dirección, no pueden aparecer en el lado izquierdo de un operador de asignación. Sin embargo, las estructuras con miembros array son lvalues modificables y pueden ser asignadas:
int a[3] = {1,2,3}, b[3] = {4,5,6}; int (*p)[3] = &a; // correcto, se puede tomar la dirección de a // a = b; // error, a es un array struct { int c[3]; } s1, s2 = {3,4,5}; s1 = s2; // correcto: se pueden asignar estructuras que contienen miembros array
Conversión de array a puntero
Cualquier expresión lvalue de tipo array, cuando se utiliza en cualquier contexto excepto
- como el operando del operador de dirección
-
como el operando de
sizeof -
como el operando de
typeofytypeof_unqual(desde C23) - como el literal de cadena usado para inicialización de arreglos
| (since C11) |
sufre una conversión implícita al puntero de su primer elemento. El resultado no es un lvalue.
Si el array fue declarado
register
, el comportamiento del programa que intenta dicha conversión es indefinido.
Cuando un tipo de array se utiliza en una lista de parámetros de función, se transforma al tipo de puntero correspondiente: int f ( int a [ 2 ] ) y int f ( int * a ) declaran la misma función. Dado que el tipo de parámetro real de la función es tipo puntero, una llamada a función con un argumento array realiza una conversión de array a puntero; el tamaño del array argumento no está disponible para la función llamada y debe pasarse explícitamente:
#include <stdio.h> void f(int a[], int sz) // en realidad declara void f(int* a, int sz) { for (int i = 0; i < sz; ++i) printf("%d\n", a[i]); } void g(int (*a)[10]) // el parámetro de puntero a array no se transforma { for (int i = 0; i < 10; ++i) printf("%d\n", (*a)[i]); } int main(void) { int a[10] = {0}; f(a, 10); // convierte a a int*, pasa el puntero g(&a); // pasa un puntero al array (no es necesario pasar el tamaño) }
Arreglos multidimensionales
Cuando el tipo de elemento de un array es otro array, se dice que el array es multidimensional:
// array de 2 arrays de 3 ints cada uno int a[2][3] = {{1,2,3}, // puede verse como una matriz 2x3 {4,5,6}}; // con disposición row-major
Tenga en cuenta que cuando se aplica la conversión de array a puntero, un array multidimensional se convierte a un puntero a su primer elemento, por ejemplo, puntero a la primera fila:
int a[2][3]; // matriz 2x3 int (*p1)[3] = a; // puntero a la primera fila de 3 elementos int b[3][3][3]; // cubo 3x3x3 int (*p2)[3][3] = b; // puntero al primer plano 3x3
|
Los arreglos multidimensionales pueden ser modificados variablemente en cada dimensión si los VLA son compatibles (desde C11) : int n = 10; int a[n][2*n]; |
(desde C99) |
Notas
Las declaraciones de arreglos de longitud cero no están permitidas, aunque algunos compiladores las ofrecen como extensiones (típicamente como una implementación pre-C99 de miembros de arreglo flexibles ).
Si la expresión de tamaño expression de un VLA tiene efectos secundarios, se garantiza que se produzcan excepto cuando forma parte de una expresión sizeof cuyo resultado no depende de ella:
int n = 5, m = 5; size_t sz = sizeof(int (*[n++])[m++]); // n se incrementa, m puede o no incrementarse
Referencias
- Estándar C23 (ISO/IEC 9899:2024):
-
- 6.7.6.2 Declaradores de arreglo (p: TBD)
- Estándar C17 (ISO/IEC 9899:2018):
-
- 6.7.6.2 Declaradores de arreglo (p: 94-96)
- Estándar C11 (ISO/IEC 9899:2011):
-
- 6.7.6.2 Declaradores de arreglo (p: 130-132)
- Estándar C99 (ISO/IEC 9899:1999):
-
- 6.7.5.2 Declaradores de arreglo (p: 116-118)
- Estándar C89/C90 (ISO/IEC 9899:1990):
-
- 3.5.4.2 Declaradores de array
Véase también
|
Documentación de C++
para
Declaración de array
|