Matriz de longitud cero

¿Qué es una matriz de longitud cero?

Como su nombre lo indica, una matriz de longitud cero es una matriz de longitud cero.

El estándar ANSI C estipula: al definir una matriz, la longitud de la matriz debe ser constante, es decir, la longitud de la matriz se determina en el momento de la compilación. El método para definir una matriz en ANSI C es el siguiente:

int  a[10];

El nuevo estándar C99 estipula que se puede definir una matriz de longitud variable.

int len;
int a[len];

Es decir, la longitud de la matriz no se determina en tiempo de compilación, solo se determina cuando el programa se está ejecutando e incluso el usuario puede especificar el tamaño. Por ejemplo, podemos definir una matriz y luego especificar el tamaño de la matriz cuando se ejecuta el programa, y ​​también podemos inicializar la matriz ingresando datos. El código de muestra es el siguiente.

int main(void)
{
    int len;

    printf("input array len:");
    scanf("%d",&len);
    int a[len];

    for(int i=0;i<len;i++)
    {
        printf("a[%d]= ",i);
        scanf("%d",&a[i]);
    }

      printf("a array print:\n");
    for(int i=0;i<len;i++)
        printf("a[%d] = %d\n",i,a[i]);

    return 0;
}

En este programa, definimos una variable len como la longitud de la matriz. Después de que se ejecute el programa, podemos especificar la longitud de la matriz por entrada e inicializarla, y finalmente imprimir los elementos de la matriz. El resultado de ejecución del programa es el siguiente:

input array len:3
a[0]= 6
a[1]= 7
a[2]= 8
a  array print:
a[0] = 6
a[1] = 7
a[2] = 8

GNU C puede pensar que las matrices de longitud variable no son divertidas, tomemos otro martillo real: admite matrices de longitud cero. ¡Ningún otro compilador es más despiadado que yo! Sí, si definimos una matriz de longitud cero en el programa, encontrará que además del compilador GCC, es posible que no se compile en otros entornos de compilación o que haya un mensaje de advertencia. La definición de una matriz de longitud cero es la siguiente:

int a[0];

Lo extraño de una matriz de longitud cero es que no ocupa espacio de almacenamiento de memoria. Usamos la palabra clave sizeof para verificar el tamaño del espacio de almacenamiento ocupado por la matriz de longitud cero en la memoria. El código es el siguiente.

int buffer[0];
int main(void)
{
    printf("%d\n", sizeof(buffer));
    return 0;
}

En este programa, definimos una matriz de longitud cero, use sizeof para ver su tamaño, puede ver: la matriz de longitud cero no ocupa espacio en la memoria, el tamaño es 0.

Las matrices de longitud cero generalmente tienen pocas oportunidades de ser utilizadas solas. A menudo se usa como miembro de una estructura para formar una estructura de longitud variable.

struct buffer{
    int len;
    int a[0];
};
int main(void)
{
      printf("%d\n",sizeof(struct buffer));
      return 0;
}

Las matrices de longitud cero tampoco ocupan espacio de almacenamiento en la estructura, por lo que el tamaño de la estructura del búfer es 4.

Ejemplo de uso de una matriz de longitud cero

Las matrices de longitud cero a menudo tienen la forma de estructuras de longitud variable y los programadores las usan en algunas aplicaciones especiales. En una estructura de longitud variable, una matriz de longitud cero no ocupa el espacio de almacenamiento de la estructura, pero podemos usar el miembro a de la estructura para acceder a la memoria, lo cual es muy conveniente. Ejemplos de uso de estructuras de longitud variable son los siguientes.

struct buffer{
    int len;
    int a[0];
};
int main(void)
{
    struct buffer *buf;
    buf = (struct buffer *)malloc \
        (sizeof(struct buffer)+ 20);

    buf->len = 20;
    strcpy(buf->a, "hello wanglitao!\n");
    puts(buf->a);

    free(buf);  
    return 0;
}

En este programa, usamos malloc para solicitar un trozo de memoria, el tamaño es sizeof (buffer) + 20, que tiene un tamaño de 24 bytes. Entre ellos, se usan 4 bytes para almacenar la variable de tipo de estructura señalada por el buf de puntero de estructura, y los otros 20 bytes son el espacio de memoria que realmente usamos. Podemos acceder directamente a esta memoria a través del miembro a de la estructura.

Mediante este método de aplicación de memoria dinámica flexible, un búfer de memoria representado por esta estructura de búfer se puede ajustar en cualquier momento y puede ser grande o pequeño. Esta característica es muy útil en algunas ocasiones. Por ejemplo, muchos sitios de video en línea ahora admiten reproducción de video en múltiples formatos: General Clear, High Definition, Ultra Clear, 1080P, Blu-ray e incluso 4K. Si nuestro programa local necesita solicitar un buffer en la memoria para almacenar los datos de video decodificados, entonces el tamaño del buffer requerido para diferentes formatos de reproducción es diferente. Si solicitamos memoria de acuerdo con el estándar 4K, cuando reproduzcamos videos de definición general, no necesitaremos un búfer tan grande y desperdiciaremos memoria en vano. Usando estructuras de longitud variable, podemos aplicar de manera flexible buffers de diferentes tamaños de acuerdo con la configuración del formato de reproducción del usuario, lo que ahorra mucho espacio de memoria.

El uso de matrices de longitud cero en el núcleo

Las matrices de longitud cero generalmente se usan en forma de estructuras de longitud variable en el núcleo. Hoy analizaremos el controlador USB en el kernel de Linux. En el controlador de la tarjeta de red, todos pueden estar familiarizados con un nombre: buffer de socket, es decir, buffer de socket, utilizado para transmitir paquetes de datos de red. Del mismo modo, en el controlador USB, hay una cosa similar, llamada URB, cuyo nombre completo es bloque de solicitud USB, es decir, bloque de solicitud USB, que se utiliza para transferir paquetes de datos USB.

struct urb {
    struct kref kref;
    void *hcpriv;
    atomic_t use_count;
    atomic_t reject;
    int unlinked;

    struct list_head urb_list;
    struct list_head anchor_list;
    struct usb_anchor *anchor;
    struct usb_device *dev;
    struct usb_host_endpoint *ep;
    unsigned int pipe;
    unsigned int stream_id;
    int status;
    unsigned int transfer_flags;
    void *transfer_buffer;
    dma_addr_t transfer_dma;
    struct scatterlist *sg;
    int num_mapped_sgs;
    int num_sgs;
    u32 transfer_buffer_length;
    u32 actual_length;
    unsigned char *setup_packet;
    dma_addr_t setup_dma;
    int start_frame;
    int number_of_packets;
    int interval;

    int error_count;
    void *context;
    usb_complete_t complete;
    struct usb_iso_packet_descriptor iso_frame_desc[0];
};

En esta estructura, se definen la dirección de transmisión, la dirección de transmisión, el tamaño de transmisión y el modo de transmisión del paquete de datos USB. No profundizamos en estos detalles, solo miramos al último miembro:

struct usb_iso_packet_descriptor iso_frame_desc[0];

Al final de la estructura URB, defina una matriz de longitud cero, utilizada principalmente para la transmisión síncrona USB. El USB tiene 4 modos de transmisión: transmisión de interrupción, transmisión de control, transmisión por lotes y transmisión síncrona. Los diferentes dispositivos USB tienen diferentes requisitos de velocidad de transmisión y seguridad de los datos de transmisión, y los modos de transmisión utilizados son diferentes. La cámara USB tiene altos requisitos para la transmisión en tiempo real de video o imágenes. No le importa la pérdida de datos de cuadros. No importa si se pierde un cuadro, y luego se descarga. Entonces, la cámara USB adopta el modo de transmisión síncrona USB.

Ahora la cámara USB en Taobao, abre su manual, generalmente admite múltiples resoluciones: desde 16 * 16 a HD 720P en múltiples formatos. Para la transmisión de video con diferentes resoluciones, para un marco de datos de imagen, el tamaño y la cantidad de paquetes de datos de transmisión USB son diferentes. ¿Cómo debe diseñarse el USB para adaptarse a los requisitos de transmisión de datos de diferentes tamaños, pero no afecta a otros modos de transmisión USB? La respuesta se encuentra en esta matriz de longitud cero en la estructura.

Cuando los usuarios establecen diferentes resoluciones para transmitir video, USB necesita usar diferentes tamaños y números de paquetes de datos para transmitir un cuadro de datos de video. Esta estructura de longitud variable formada por una matriz de longitud cero puede cumplir este requisito. De acuerdo con el tamaño de un marco de datos de imagen, puede solicitar de manera flexible espacio en la memoria para cumplir con la transmisión de datos de diferentes tamaños. Sin embargo, esta matriz de longitud cero no ocupa el espacio de almacenamiento de la estructura. Cuando el USB usa otros modos para la transmisión, no se ve afectado de ninguna manera, y es muy posible que esta matriz de longitud cero no exista. ¡Entonces, tengo que decir que tal diseño es realmente maravilloso!

Pensamiento: ¿por qué no usar punteros en lugar de matrices de longitud cero?

En varias ocasiones, a menudo puede ver tales palabras: cuando el nombre de la matriz se pasa como un parámetro de función, es equivalente a un puntero. Aquí, no debemos confundirnos con esta oración: cuando el nombre de la matriz se pasa como un parámetro de función, de hecho se pasa una dirección, pero el nombre de la matriz nunca es un puntero, y los dos no son lo mismo. El nombre de la matriz se utiliza para caracterizar la dirección de un espacio de almacenamiento de memoria continuo y el puntero es una variable. El compilador debe asignarle un espacio de memoria separado para almacenar la dirección de la variable a la que apunta. Veamos este programa a continuación.

struct buffer1{
    int len;
    int a[0];
};
struct buffer2{
    int len;
    int *a;
};
int main(void)
{
    printf("buffer1: %d\n", sizeof(struct buffer1));
    printf("buffer2: %d\n", sizeof(struct buffer2));
    return 0;
}

Los resultados son los siguientes:

buffer1:4
buffer2:8

Para una variable de puntero, el compilador debe asignar un espacio de almacenamiento para la variable de puntero por separado, y luego almacenar la dirección de otra variable en este espacio de almacenamiento. Diremos que el puntero apunta a esta variable. El nombre de la matriz, el compilador no asignará un espacio de almacenamiento, es solo un símbolo, como el nombre de la función, utilizado para representar una dirección. A continuación miramos otro programa.

//hello.c
int array1[10] ={1,2,3,4,5,6,7,8,9};
int array2[0];
int *p = &array1[5];
int main(void)
{
    return 0;
}

En este programa, definimos una matriz ordinaria, una matriz de longitud cero y una variable de puntero. El valor de esta variable de puntero p es la dirección del elemento de matriz array1 [5], lo que significa que el puntero p apunta a arrayay1 [5]. Luego compilamos y desensamblamos este programa usando el compilador cruzado de brazo.

$ arm-linux-gnueabi-gcc hello.c -o a.out
$ arm-linux-gnueabi-objdump -D a.out

Del código de ensamblaje generado por el desmontaje, encontramos el código de ensamblaje de array1 y la variable de puntero p.

00021024 <array1>:
   21024:    00000001    andeq   r0, r0, r1
   21028:    00000002    andeq   r0, r0, r2
   2102c:    00000003    andeq   r0, r0, r3
   21030:    00000004    andeq   r0, r0, r4
   21034:    00000005    andeq   r0, r0, r5
   21038:    00000006    andeq   r0, r0, r6
   2103c:    00000007    andeq   r0, r0, r7
   21040:    00000008    andeq   r0, r0, r8
   21044:    00000009    andeq   r0, r0, r9
   21048:    00000000    andeq   r0, r0, r0
0002104c <p>:
   2104c:    00021038    andeq   r1, r2, r8, lsr r0
Disassembly of section .bss:

00021050 <__bss_start>:
   21050:    00000000    andeq   r0, r0, r0

A partir del código de ensamblaje, podemos ver que para la matriz de longitud 10 array1 [10], el compilador asignó 40 bytes de espacio de almacenamiento desde 0x21024--0x21048, pero no asignó almacenamiento para el nombre de matriz array1 por separado. Espacio, el nombre de matriz array1 solo representa la primera dirección de los 40 espacios de almacenamiento consecutivos, es decir, la dirección del elemento de matriz array1 [0]. Para array2 [0], una matriz de longitud cero, el compilador no le asigna espacio de almacenamiento. En este momento, array2 es solo un símbolo utilizado para representar una dirección en memoria. Podemos verificar el archivo ejecutable a.out Para encontrar este valor de dirección.

$ readelf -s  a.out
    88: 00021024    40 OBJECT  GLOBAL DEFAULT   23 array1
    89: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 _bss_end__
    90: 00021050     0 NOTYPE  GLOBAL DEFAULT   23 _edata
    91: 0002104c     4 OBJECT  GLOBAL DEFAULT   23 p
    92: 00010480     0 FUNC    GLOBAL DEFAULT   14 _fini
    93: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 __bss_end__
    94: 0002101c     0 NOTYPE  GLOBAL DEFAULT   23 __data_start_
    96: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    97: 00021020     0 OBJECT  GLOBAL HIDDEN    23 __dso_handle
    98: 00010488     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    99: 0001041c    96 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    100: 00021054     0 OBJECT  GLOBAL DEFAULT   24 array2
    101: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 _end
    102: 000102d8     0 FUNC    GLOBAL DEFAULT   13 _start
    103: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 __end__
    104: 00021050     0 NOTYPE  GLOBAL DEFAULT   24 __bss_start
    105: 00010400    28 FUNC    GLOBAL DEFAULT   13 main
    107: 00021050     0 OBJECT  GLOBAL HIDDEN    23 __TMC_END__
    110: 00010294     0 FUNC    GLOBAL DEFAULT   11 _init

Como puede ver en la tabla de símbolos, la dirección de array2 es 0x21054, que está detrás de la sección bss del programa. La dirección predeterminada indicada por el símbolo de matriz2 es un espacio de memoria no utilizado, nada más, el compilador nunca asignará un espacio de memoria para almacenar el nombre de la matriz. Al ver esto, es posible que comprenda que los nombres de matriz y los punteros no son lo mismo. Aunque los nombres de matriz pueden usarse como una dirección cuando se usan como parámetros de función, no pueden ser iguales. Los helicópteros a veces se pueden usar como armas, pero no se puede decir que los helicópteros son armas.

En cuanto a por qué no usar punteros, es muy simple. Si usa un puntero, el puntero en sí mismo también ocupará espacio de almacenamiento. Sin mencionar que, de acuerdo con el análisis de caso del controlador USB anterior, encontrará que es mucho menos inteligente que una matriz de longitud cero: no causará redundancia en la definición de la estructura, También es muy conveniente de usar.

发布了81 篇原创文章 · 获赞 69 · 访问量 5万+

Supongo que te gusta

Origin blog.csdn.net/s2603898260/article/details/103657907
Recomendado
Clasificación