No he usado mucho C en los últimos años. Cuando leí esta pregunta hoy me encontré con una sintaxis de C con la que no estaba familiarizado.
Aparentemente en C99 la siguiente sintaxis es válida:
void foo(int n) {
int values[n]; //Declare a variable length array
}
Esto parece una característica bastante útil. ¿Hubo alguna vez una discusión sobre cómo agregarlo al estándar C ++ y, de ser así, por qué se omitió?
Algunas posibles razones:
- Peludo para los vendedores de compiladores para implementar
- Incompatible con alguna otra parte del estándar.
- La funcionalidad se puede emular con otras construcciones de C ++
El estándar C ++ establece que el tamaño de la matriz debe ser una expresión constante (8.3.4.1).
Sí, por supuesto, me doy cuenta de que en el ejemplo del juguete se podría usar std::vector<int> values(m);
, pero esto asigna memoria del montón y no de la pila. Y si quiero una matriz multidimensional como:
void foo(int x, int y, int z) {
int values[x][y][z]; // Declare a variable length array
}
la vector
versión se vuelve bastante torpe:
void foo(int x, int y, int z) {
vector< vector< vector<int> > > values( /* Really painful expression here. */);
}
Los sectores, filas y columnas también se extenderán potencialmente por toda la memoria.
Al observar la discusión comp.std.c++
, está claro que esta pregunta es bastante controvertida con algunos nombres muy pesados en ambos lados de la discusión. Ciertamente no es obvio que a std::vector
es siempre una mejor solución.
fuente
Respuestas:
Recientemente hubo una discusión sobre esto que comenzó en Usenet: ¿Por qué no hay VLA en C ++ 0x ?
Estoy de acuerdo con aquellas personas que parecen estar de acuerdo en que tener que crear una gran matriz potencial en la pila, que generalmente tiene poco espacio disponible, no es bueno. El argumento es que, si conoce el tamaño de antemano, puede usar una matriz estática. Y si no conoce el tamaño de antemano, escribirá un código inseguro.
Los CLA VLA podrían proporcionar un pequeño beneficio al poder crear arreglos pequeños sin desperdiciar espacio o llamar a constructores para elementos no utilizados, pero introducirán cambios bastante grandes en el sistema de tipos (debe poder especificar tipos dependiendo de los valores de tiempo de ejecución) aún no existe en C ++ actual, a excepción de
new
los especificadores de tipo de operador, pero se tratan especialmente, de modo que el tiempo de ejecución no escapa al alcance delnew
operador).Puede usar
std::vector
, pero no es lo mismo, ya que usa memoria dinámica, y hacer que use el propio asignador de pila no es exactamente fácil (la alineación también es un problema). Tampoco resuelve el mismo problema, porque un vector es un contenedor redimensionable, mientras que los VLA son de tamaño fijo. La propuesta de C ++ Dynamic Array tiene la intención de introducir una solución basada en biblioteca, como alternativa a un VLA basado en lenguaje. Sin embargo, no será parte de C ++ 0x, que yo sepa.fuente
T(*)[]
a aT(*)[N]
- en C ++ esto no está permitido, ya que C ++ no sabe sobre "compatibilidad de tipos": requiere coincidencias exactas), parámetros de tipo, excepciones, con- y destructores y otras cosas. No estoy seguro de si los beneficios de los VLA realmente pagarían todo ese trabajo. Pero entonces, nunca he usado VLA en la vida real, por lo que probablemente no conozco buenos casos de uso para ellos.vector
pero requiere un patrón de uso de LIFO fijo y mantiene uno o más buffers asignados estáticamente por subproceso que generalmente se dimensionan de acuerdo con la asignación total más grande que tiene el subproceso alguna vez utilizado, pero que podría recortarse explícitamente. En el caso común, una "asignación" normal no requeriría más que una copia de puntero, sustracción de puntero de puntero, comparación de enteros y adición de puntero; la desasignación simplemente requeriría una copia del puntero. No mucho más lento que un VLA.(Antecedentes: tengo algo de experiencia en la implementación de compiladores C y C ++).
Las matrices de longitud variable en C99 fueron básicamente un paso en falso. Para soportar los VLA, C99 tuvo que hacer las siguientes concesiones al sentido común:
sizeof x
ya no es siempre una constante de tiempo de compilación; el compilador a veces debe generar código para evaluar unasizeof
expresión en tiempo de ejecución.Permitir VLA bidimensionales (
int A[x][y]
) necesaria una nueva sintaxis para declarar funciones que toman 2D VLA como parámetros:void foo(int n, int A[][*])
.Menos importante en el mundo de C ++, pero extremadamente importante para el público objetivo de programadores de sistemas integrados de C, declarar un VLA significa cortar una porción arbitrariamente grande de su pila. Este es un desbordamiento de pila y un bloqueo garantizados . (Cada vez que declaras
int A[n]
, estás afirmando implícitamente que tienes 2GB de pila de sobra. Después de todo, si sabes que "n
definitivamente es menos de 1000 aquí", entonces simplemente declararíasint A[1000]
. Sustituyendo el entero de 32 bitsn
para1000
es una admisión que no tienes idea de cuál debería ser el comportamiento de tu programa).Bien, pasemos ahora a hablar de C ++. En C ++, tenemos la misma distinción fuerte entre "sistema de tipos" y "sistema de valores" que C89 ... pero realmente hemos comenzado a confiar en ella de formas que C no tiene. Por ejemplo:
Si
n
no fuera una constante de tiempo de compilación (es decir, siA
fuera de tipo variablemente modificado), entonces ¿cuál sería el tipo deS
? WouldS
tipo 's también se determinaría solo en tiempo de ejecución?¿Qué hay de esto?
El compilador debe generar código para alguna instanciación de
myfunc
. ¿Cómo debería ser ese código? ¿Cómo podemos generar estáticamente ese código, si no sabemos el tipo deA1
en tiempo de compilación?Peor, ¿y si resulta que en tiempo de ejecución eso
n1 != n2
, así que!std::is_same<decltype(A1), decltype(A2)>()
? En ese caso, la llamada amyfunc
ni siquiera debería compilar , ¡porque la deducción de tipo de plantilla debería fallar! ¿Cómo podríamos emular ese comportamiento en tiempo de ejecución?Básicamente, C ++ se está moviendo en la dirección de impulsar más y más decisiones en tiempo de compilación : generación de código de plantilla,
constexpr
evaluación de funciones, etc. Mientras tanto, C99 estaba ocupado empujando decisiones tradicionalmente en tiempo de compilación (por ejemplosizeof
) en el tiempo de ejecución . Con esto en mente, ¿tiene sentido gastar algún esfuerzo? tratando de integrar VLA de estilo C99 en C ++?Como todos los demás respondedores ya han señalado, C ++ proporciona muchos mecanismos de asignación de almacenamiento dinámico (
std::unique_ptr<int[]> A = new int[n];
ostd::vector<int> A(n);
son los obvios) cuando realmente desea transmitir la idea "No tengo idea de la cantidad de RAM que podría necesitar". Y C ++ proporciona un ingenioso modelo de manejo de excepciones para lidiar con la situación inevitable de que la cantidad de RAM que necesita es mayor que la cantidad de RAM que tiene. Pero es de esperar que esta respuesta le dé una buena idea de por qué los VLA de estilo C99 no se ajustaban bien a C ++, y ni siquiera se ajustaban bien a C99. ;)Para obtener más información sobre el tema, consulte N3810 "Alternativas para extensiones de matriz" , documento de Bjarne Stroustrup de octubre de 2013 sobre VLA. El punto de vista de Bjarne es muy diferente al mío; N3810 se enfoca más en encontrar una buena sintaxis ish de C ++ para las cosas y en desalentar el uso de matrices en bruto en C ++, mientras que me concentré más en las implicaciones para la metaprogramación y el sistema de tipos. No sé si considera las implicaciones de metaprogramación / sistema de tipos resueltas, solucionables o simplemente sin interés.
Una buena publicación de blog que alcanza muchos de estos mismos puntos es "Uso legítimo de matrices de longitud variable" (Chris Wellons, 2019-10-27).
fuente
alloca()
debería haberse estandarizado en C99. Los VLA son lo que sucede cuando un comité de estándares se adelanta a las implementaciones, en lugar de al revés.*
es opcional, puede (y debe) escribirint A[][n]
; (3) Puede usar el sistema de tipos sin declarar realmente ningún VLA. Por ejemplo, una función puede aceptar una matriz de tipo modificado de forma variable, y se puede invocar con matrices 2-D que no sean VLA de diferentes dimensiones. Sin embargo, haces puntos válidos en la última parte de tu publicación.n
en su caso de prueba y cuál era el tamaño de su pila? Le sugiero que intente ingresar un valor por lon
menos tan grande como el tamaño de su pila. (Y si no hay forma de que el usuario controle el valor den
su programa, le sugiero que simplemente propague el valor máximo den
directamente en la declaración: declareint A[1000]
o lo que sea que necesite. Los VLA solo son necesarios y solo peligrosos, cuando el valor máximo den
no está limitado por una pequeña constante de tiempo de compilación.)Siempre puede usar alloca () para asignar memoria en la pila en tiempo de ejecución, si lo desea:
Ser asignado en la pila implica que se liberará automáticamente cuando la pila se desenrolle.
Nota rápida: Como se menciona en la página de manual de Mac OS X para alloca (3), "La función alloca () depende de la máquina y el compilador; su uso no está recomendado". Solo para que sepas.
fuente
if (!p) { p = alloca(strlen(foo)+1); strcpy(p, foo); }
esto no se puede hacer con VLA, precisamente debido a su alcance de bloque.C
solución similar, y no realmenteC++
-ish.En mi propio trabajo, me di cuenta de que cada vez que quería algo como arreglos automáticos de longitud variable o alloca (), realmente no me importaba que la memoria estuviera ubicada físicamente en la pila de la CPU, solo que provenía de algún asignador de pila que no incurrió en viajes lentos al montón general. Por lo tanto, tengo un objeto por subproceso que posee algo de memoria desde la cual puede empujar / explotar buffers de tamaño variable. En algunas plataformas, permito que esto crezca a través de mmu. Otras plataformas tienen un tamaño fijo (generalmente acompañado de una pila de CPU de tamaño fijo también porque no mmu). Una plataforma con la que trabajo (una consola de juegos portátil) tiene una pequeña pila de CPU preciosa de todos modos porque reside en la memoria escasa y rápida.
No digo que nunca sea necesario empujar buffers de tamaño variable en la pila de CPU. Honestamente, me sorprendí cuando descubrí que esto no era estándar, ya que ciertamente parece que el concepto se ajusta bastante bien al lenguaje. Sin embargo, para mí, los requisitos "tamaño variable" y "deben estar ubicados físicamente en la pila de la CPU" nunca se han unido. Se trata de la velocidad, así que hice mi propio tipo de "pila paralela para memorias intermedias de datos".
fuente
Hay situaciones en las que asignar memoria de almacenamiento dinámico es muy costoso en comparación con las operaciones realizadas. Un ejemplo es la matriz matemática. Si trabaja con matrices más pequeñas, diga de 5 a 10 elementos y haga muchas operaciones aritméticas, la sobrecarga de malloc será realmente significativa. Al mismo tiempo, hacer que el tamaño sea un tiempo de compilación constante parece muy derrochador e inflexible.
Creo que C ++ es tan inseguro en sí mismo que el argumento de "tratar de no agregar más características inseguras" no es muy fuerte. Por otro lado, dado que C ++ es posiblemente las características del lenguaje de programación más eficientes en tiempo de ejecución, lo que lo hace aún más útil siempre: las personas que escriben programas críticos para el rendimiento usarán en gran medida C ++, y necesitan tanto rendimiento como sea posible. Mover cosas del montón al montón es una de esas posibilidades. Reducir el número de bloques de montón es otra. Permitir VLA como miembros de objeto sería una forma de lograr esto. Estoy trabajando en tal sugerencia. Es un poco complicado de implementar, sin duda, pero parece bastante factible.
fuente
Parece que estará disponible en C ++ 14:
https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays
Actualización: no llegó a C ++ 14.
fuente
Esto se consideró para su inclusión en C ++ / 1x, pero se descartó (esto es una corrección de lo que dije anteriormente).
Sería menos útil en C ++ de todos modos ya que ya tenemos
std::vector
que cumplir este rol.fuente
std::vector
en lugar de, por ejemplo,alloca()
.Use std :: vector para esto. Por ejemplo:
La memoria se asignará en el montón, pero esto solo tiene un pequeño inconveniente de rendimiento. Además, es aconsejable no asignar grandes bloques de datos en la pila, ya que su tamaño es bastante limitado.
fuente
std::vector<int> values(n);
? Al usarresize
después de la construcción, está prohibiendo los tipos no móviles.C99 permite VLA. Y pone algunas restricciones sobre cómo declarar VLA. Para más detalles, consulte 6.7.5.2 de la norma. C ++ no permite VLA. Pero g ++ lo permite.
fuente
Las matrices como esta son parte de C99, pero no parte de C ++ estándar. Como otros han dicho, un vector siempre es una solución mucho mejor, por lo que probablemente las matrices de tamaño variable no están en el estándar C ++ (o en el estándar C ++ 0x propuesto).
Por cierto, para preguntas sobre "por qué" el estándar C ++ es como es, el grupo de noticias moderado Usenet comp.std.c ++ es el lugar al que debe dirigirse.
fuente
Si conoce el valor en tiempo de compilación, puede hacer lo siguiente:
Editar: puede crear un vector que utilice un asignador de pila (alloca), ya que el asignador es un parámetro de plantilla.
fuente
Tengo una solución que realmente funcionó para mí. No quería asignar memoria debido a la fragmentación en una rutina que necesitaba ejecutarse muchas veces. La respuesta es extremadamente peligrosa, así que úsela bajo su propio riesgo, pero aprovecha el montaje para reservar espacio en la pila. Mi ejemplo a continuación utiliza una matriz de caracteres (obviamente, otra variable de tamaño requeriría más memoria).
Los peligros aquí son muchos, pero explicaré algunos: 1. Cambiar el tamaño de la variable a la mitad eliminaría la posición de la pila 2. Sobrepasar los límites de la matriz destruiría otras variables y el posible código 3. Esto no funciona en 64 bits construir ... necesita un ensamblaje diferente para ese (pero una macro podría resolver ese problema). 4. Compilador específico (puede tener problemas para moverse entre compiladores). No lo he intentado, así que realmente no lo sé.
fuente
esp
cambió y ajustará sus accesos a la pila, pero, por ejemplo, en GCC simplemente lo romperá por completo, al menos si usa optimizaciones y-fomit-frame-pointer
en particular.