Namespaces
Variants

Array declaration

From cppreference.net

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)
1,2) Sintaxis general del declarador de arreglo
3) Declarador para VLA de tamaño no especificado (puede aparecer únicamente en el ámbito de prototipo de función) donde
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 static y qualifiers , que pueden aparecer en cualquier orden antes de la expresión de tamaño (también pueden aparecer incluso cuando se omite la expresión de tamaño).

En cada llamada a función a una función donde un parámetro de array utiliza la palabra clave static entre [ y ] , el valor del parámetro real debe ser un puntero válido al primer elemento de un array con al menos tantos elementos como los especificados por expresión :

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 restrict calificador de tipo:

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 variable

Si 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.

#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 * , la declaración es para un VLA de tamaño no especificado. Tal declaración solo puede aparecer en el ámbito del prototipo de función, y declara un array de tipo completo. De hecho, todos los declaradores VLA en el ámbito del prototipo de función se tratan como si expression fuera reemplazada por * .

void foo(size_t x, int a[*]);
void foo(size_t x, int a[x])
{
    printf("%zu\n", sizeof a); // igual que sizeof(int*)
}

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 const , volatile , o restrict (desde C99) (lo cual es posible mediante el uso de typedef ), el tipo de array no está calificado, pero su tipo de elemento sí lo está:

(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 _Atomic .

(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

_Atomic no se permite aplicar a un tipo de array, aunque se permite un array de tipo atómico.

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

(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.

int a[3] = {1,2,3};
int* p = a;
printf("%zu\n", sizeof a); // imprime el tamaño del array
printf("%zu\n", sizeof p); // imprime el tamaño de un puntero

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