Esto es lo que encontré durante mi período de aprendizaje:
#include<iostream>
using namespace std;
int dis(char a[1])
{
int length = strlen(a);
char c = a[2];
return length;
}
int main()
{
char b[4] = "abc";
int c = dis(b);
cout << c;
return 0;
}
Entonces, en la variable int dis(char a[1])
, [1]
parece no hacer nada y no funciona en
absoluto, porque puedo usar a[2]
. Justo como int a[]
o char *a
. Sé que el nombre de la matriz es un puntero y cómo transmitir una matriz, por lo que mi rompecabezas no se trata de esta parte.
Lo que quiero saber es por qué los compiladores permiten este comportamiento ( int a[1]
). ¿O tiene otros significados que no conozco?
typedef
tipo de matriz. Por lo que la "decadencia de puntero" en los tipos de argumentos no es sólo azúcar sintáctico reemplazar[]
con*
, que realmente va a través del sistema de tipos. Esto tiene consecuencias en el mundo real para algunos tipos estándar como losva_list
que se pueden definir con tipo de matriz o sin matriz.int dis(char (*a)[1])
. A continuación, se pasa un puntero a una matriz:dis(&b)
. Si está dispuesto a usar características de C que no existen en C ++, también puede decir cosas comovoid foo(int data[static 256])
yint bar(double matrix[*][*])
, pero esa es una lata completamente diferente de gusanos.Respuestas:
Es una peculiaridad de la sintaxis para pasar matrices a funciones.
En realidad, no es posible pasar una matriz en C. Si escribe una sintaxis que parece que debería pasar la matriz, lo que realmente sucede es que se pasa un puntero al primer elemento de la matriz.
Como el puntero no incluye ninguna información de longitud, los contenidos de su
[]
en la lista de parámetros formales de la función se ignoran realmente.La decisión de permitir esta sintaxis se tomó en la década de 1970 y ha causado mucha confusión desde entonces ...
fuente
void foo(int (*somearray)[20])
sintaxis. en este caso, 20 se aplica en los sitios de la persona que llama.[]
no se ignoran en matrices multidimensionales como se muestra en la respuesta de pat. Por lo tanto, era necesario incluir la sintaxis de la matriz. Además, nada impide que el compilador emita advertencias incluso en matrices unidimensionales.void foo(int (*args)[20]);
también, estrictamente hablando, C no tiene matrices multidimensionales; pero tiene matrices cuyos elementos pueden ser otras matrices. Esto no cambia nada.Se ignora la longitud de la primera dimensión, pero la longitud de las dimensiones adicionales son necesarias para permitir que el compilador calcule las compensaciones correctamente. En el siguiente ejemplo, la
foo
función pasa un puntero a una matriz bidimensional.El tamaño de la primera dimensión
[10]
se ignora; el compilador no le impedirá indexar al final (tenga en cuenta que el formal quiere 10 elementos, pero el real solo proporciona 2). Sin embargo, el tamaño de la segunda dimensión[20]
se usa para determinar el paso de cada fila, y aquí, el formal debe coincidir con el real. Nuevamente, el compilador tampoco le impedirá indexar el final de la segunda dimensión.El desplazamiento de bytes desde la base de la matriz a un elemento
args[row][col]
está determinado por:Tenga en cuenta que si
col >= 20
, en realidad se indexará en una fila posterior (o fuera del final de toda la matriz).sizeof(args[0])
, vuelve80
a mi máquina dondesizeof(int) == 4
. Sin embargo, si intento tomarsizeof(args)
, me sale la siguiente advertencia del compilador:Aquí, el compilador advierte que solo dará el tamaño del puntero en el que la matriz se ha descompuesto en lugar del tamaño de la matriz misma.
fuente
args
. En este caso, el primer elemento de args es una "matriz de 20 pulgadas". Los punteros incluyen información de tipo; lo que se pasa es "puntero a una matriz de 20 pulgadas".int (*)[20]
tipo; "puntero a una matriz de 20 ints".El problema y cómo superarlo en C ++
El problema ha sido explicado ampliamente por Pat y Matt . El compilador básicamente ignora la primera dimensión del tamaño de la matriz, ignorando efectivamente el tamaño del argumento pasado.
En C ++, por otro lado, puede superar fácilmente esta limitación de dos maneras:
std::array
(desde C ++ 11)Referencias
Si su función solo intenta leer o modificar una matriz existente (sin copiarla), puede usar referencias fácilmente.
Por ejemplo, supongamos que desea tener una función que restablezca una matriz de diez
int
s configurando cada elemento0
. Puede hacerlo fácilmente utilizando la siguiente firma de función:No solo esto funcionará bien , sino que también impondrá la dimensión de la matriz .
También puede utilizar plantillas para hacer que el código anterior sea genérico :
Y finalmente puedes aprovechar la
const
corrección. Consideremos una función que imprime una matriz de 10 elementos:Al aplicar el
const
calificador estamos evitando posibles modificaciones .La clase de biblioteca estándar para matrices.
Si considera que la sintaxis anterior es fea e innecesaria, como lo hago yo, podemos lanzarla a la lata y usarla
std::array
en su lugar (desde C ++ 11).Aquí está el código refactorizado:
¿No es maravilloso? Sin mencionar que el truco de código genérico que te enseñé anteriormente, todavía funciona:
No solo eso, sino que obtienes copias y movimientos semánticos gratis. :)
¿Entonces, Qué esperas? Ve a usar
std::array
.fuente
Es una característica divertida de C que te permite dispararte efectivamente en el pie si estás tan inclinado.
Creo que la razón es que C está solo un paso por encima del lenguaje ensamblador. La comprobación de tamaño y características de seguridad similares se han eliminado para permitir el máximo rendimiento, lo que no es malo si el programador es muy diligente.
Además, asignar un tamaño al argumento de la función tiene la ventaja de que cuando otro programador utiliza la función, existe la posibilidad de que noten una restricción de tamaño. Solo usar un puntero no transmite esa información al siguiente programador.
fuente
Primero, C nunca verifica los límites de la matriz. No importa si son parámetros locales, globales, estáticos, lo que sea. Verificar los límites de la matriz significa más procesamiento, y se supone que C es muy eficiente, por lo que el programador realiza la verificación de los límites de la matriz cuando es necesario.
En segundo lugar, hay un truco que hace posible pasar una matriz por valor a una función. También es posible devolver por valor una matriz de una función. Solo necesita crear un nuevo tipo de datos usando struct. Por ejemplo:
Tienes que acceder a los elementos de esta manera: foo.a [1]. El ".a" adicional puede parecer extraño, pero este truco agrega una gran funcionalidad al lenguaje C.
fuente
Para decirle al compilador que myArray apunta a una matriz de al menos 10 ints:
Un buen compilador debería avisarle si accede a myArray [10]. Sin la palabra clave "estática", el 10 no significaría nada en absoluto.
fuente
[static]
permite que el compilador advierta si llamabar
con unint[5]
. No dicta lo que puede acceder dentrobar
. La responsabilidad es totalmente del lado de la persona que llama.error: expected primary-expression before 'static'
Nunca he visto esta sintaxis. es poco probable que sea C o C ++ estándar.Esta es una "característica" bien conocida de C, que se pasa a C ++ porque se supone que C ++ compila correctamente el código C.
El problema surge de varios aspectos:
Se podría decir que las matrices no son realmente compatibles en C (esto no es realmente cierto, como decía antes, pero es una buena aproximación); Una matriz se trata realmente como un puntero a un bloque de datos y se accede mediante la aritmética del puntero. Como C NO tiene ninguna forma de RTTI, debe declarar el tamaño del elemento de matriz en el prototipo de la función (para admitir la aritmética del puntero). Esto es incluso "más cierto" para las matrices multidimensionales.
De todos modos, todo lo anterior ya no es realmente cierto: p
La mayoría de los compiladores modernos C / C ++ hacen los límites de soporte de cheques, pero las normas exigen que sea desactivada por defecto (por compatibilidad con versiones anteriores). Las versiones razonablemente recientes de gcc, por ejemplo, realizan la comprobación del rango de tiempo de compilación con "-O3 -Wall -Wextra" y la comprobación de límites de tiempo de ejecución completo con "-fbounds-Check".
fuente
struct MyStruct s = { .field1 = 1, .field2 = 2 };
) para inicializar estructuras, porque es una forma mucho más clara de inicializar una estructura. Como resultado, la mayoría del código C actual será rechazado por los compiladores estándar de C ++, porque la mayoría del código C inicializará estructuras.C no solo transformará un parámetro de tipo
int[5]
en*int
; dada la declaracióntypedef int intArray5[5];
, se va a transformar un parámetro de tipointArray5
a*int
también. Hay algunas situaciones en las que este comportamiento, aunque extraño, es útil (especialmente con cosas como lasva_list
definidas enstdargs.h
, que algunas implementaciones definen como una matriz). Sería ilógico permitir como parámetro un tipo definido comoint[5]
(ignorando la dimensión) pero no permitirint[5]
que se especifique directamente.Encuentro que el manejo de C de los parámetros de tipo de matriz es absurdo, pero es una consecuencia de los esfuerzos por adoptar un lenguaje ad-hoc, gran parte del cual no estaba particularmente bien definido o pensado, y tratar de llegar a un comportamiento. especificaciones que son consistentes con lo que hicieron las implementaciones existentes para los programas existentes. Muchas de las peculiaridades de C tienen sentido cuando se miran desde esa perspectiva, particularmente si se considera que cuando se inventaron muchas de ellas, grandes partes del lenguaje que conocemos hoy todavía no existían. Por lo que entiendo, en el predecesor de C, llamado BCPL, los compiladores realmente no registraban muy bien los tipos de variables. Una declaración
int arr[5];
era equivalente aint anonymousAllocation[5],*arr = anonymousAllocation;
; una vez que se asignó la asignación. el compilador no sabía ni le importaba siarr
era un puntero o una matriz. Cuando se accede como cualquieraarr[x]
o*arr
, se consideraría como un puntero independientemente de cómo se declarara.fuente
Una cosa que aún no ha sido respondida es la pregunta real.
Las respuestas ya dadas explican que las matrices no se pueden pasar por valor a una función en C o C ++. También explican que un parámetro declarado como
int[]
se trata como si tuviera tipoint *
, y que una variable de tipoint[]
se puede pasar a dicha función.Pero no explican por qué nunca se ha cometido un error al proporcionar explícitamente una longitud de matriz.
¿Por qué el último de estos no es un error?
Una razón para eso es que causa problemas con typedefs.
Si fuera un error especificar la longitud de la matriz en los parámetros de la función, no podría usar el
myarray
nombre en el parámetro de la función. Y dado que algunas implementaciones usan tipos de matriz para tipos de biblioteca estándar comova_list
, y se requieren todas las implementaciones para hacerjmp_buf
un tipo de matriz, sería muy problemático si no hubiera una forma estándar de declarar parámetros de función usando esos nombres: sin esa capacidad, podría haber No ser una implementación portátil de funciones comovprintf
.fuente
Está permitido que los compiladores puedan verificar si el tamaño de la matriz aprobada es el mismo que el esperado. Los compiladores pueden advertir un problema si no es el caso.
fuente