new - C++ no te dice el tamaño de una matriz dinámica. ¿Pero por qué?
matriz dinamica c++ (7)
A menudo encontrará que los administradores de memoria solo asignarán espacio en un determinado múltiplo de 64 bytes, por ejemplo.
Por lo tanto, puede solicitar un nuevo int [4], es decir, 16 bytes, pero el administrador de memoria asignará 64 bytes para su solicitud. Para liberar esta memoria no necesita saber cuánta memoria solicitó, solo que le ha asignado un bloque de 64 bytes.
La siguiente pregunta puede ser, ¿no puede almacenar el tamaño solicitado? Esta es una sobrecarga adicional que no todos están dispuestos a pagar. Un Arduino Uno, por ejemplo, solo tiene 2k de RAM, y en ese contexto, 4 bytes para cada asignación de repente se vuelven significativos.
Si necesita esa funcionalidad, entonces tiene std :: vector (o equivalente), o tiene lenguajes de nivel superior. C / C ++ fue diseñado para permitirle trabajar con tan poca sobrecarga como elija usar, este es un ejemplo.
Sé que no hay forma en C ++ de obtener el tamaño de una matriz creada dinámicamente, como:
int* a;
a = new int[n];
Lo que me gustaría saber es: ¿Por qué? ¿La gente simplemente olvidó esto en la especificación de C ++, o hay una razón técnica para esto?
¿No se almacena la información en alguna parte? Después de todo, el comando
delete[] a;
parece saber cuánta memoria tiene que liberar, por lo que me parece que
delete[]
tiene alguna forma de saber el tamaño de
a
.
El tamaño de una matriz asignada con
new[]
no se almacena
visiblemente en
ningún lugar, por lo que no puede acceder a él.
Y el
new[]
operador
new[]
no devuelve una matriz, solo un puntero al primer elemento de la matriz.
Si desea conocer el tamaño de una matriz dinámica, debe almacenarla manualmente o usar clases de bibliotecas como
std::vector
Es una continuación de la regla fundamental de "no pague por lo que no necesita".
En su ejemplo,
delete[] a;
no
necesita saber el tamaño de la matriz, porque int no tiene un destructor.
Si hubieras escrito:
std::string* a;
a = new std::string[n];
...
delete [] a;
Luego, la
delete
tiene que llamar a los destructores (y necesita saber cuántos llamar), en cuyo caso el
new
debe guardar ese recuento.
Sin embargo, dado que no es
necesario
guardarlo en todas las ocasiones, Bjarne decidió no dar acceso a él.
(En retrospectiva, creo que esto fue un error ...)
Incluso con
int
por supuesto,
algo
tiene que saber sobre el tamaño de la memoria asignada, pero:
-
Muchos asignadores redondean el tamaño a un múltiplo conveniente (digamos 64 bytes) por razones de alineación y conveniencia. El asignador sabe que un bloque tiene 64 bytes de longitud, pero no sabe si eso se debe a que
n
era 1 ... o 16. -
La biblioteca de tiempo de ejecución de C ++ puede no tener acceso al tamaño del bloque asignado. Si, por ejemplo,
new
ydelete
usanmalloc
y estánfree
, entonces la biblioteca C ++ no tiene forma de saber el tamaño de un bloque devuelto pormalloc
. (Por lo general, por supuesto,new
ymalloc
son parte de la misma biblioteca, pero no siempre).
Hay un curioso caso de sobrecarga de la
operator delete
del
operator delete
que
found
en forma de:
void operator delete[](void *p, size_t size);
El
tamaño del
parámetro parece ser el
tamaño
predeterminado (en bytes) del bloque de memoria al que void * p apunta.
Si esto es cierto, es razonable al menos esperar que tenga un valor pasado por la invocación del
operator new
y, por lo tanto, simplemente debería dividirse por
sizeof (type)
para entregar el número de elementos almacenados en la matriz.
En cuanto a la parte del "por qué" de su pregunta, la regla de Martin de "no pague por lo que no necesita" parece la más lógica.
No hay forma de saber cómo va a usar esa matriz. El tamaño de la asignación no coincide necesariamente con el número del elemento, por lo que no puede simplemente usar el tamaño de la asignación (incluso si estuviera disponible).
Esta es una falla profunda en otros lenguajes no en C ++. Usted logra la funcionalidad que desea con std :: vector y aún conserva el acceso sin formato a las matrices. Retener ese acceso sin procesar es crítico para cualquier código que realmente tenga que hacer algún trabajo.
Muchas veces realizará operaciones en subconjuntos de la matriz y cuando tenga una contabilidad adicional incorporada en el lenguaje, tendrá que reasignar las sub-matrices y copiar los datos para manipularlos con una API que espera una matriz administrada.
Solo considere el caso trillado de ordenar los elementos de datos. Si ha manejado matrices, entonces no puede usar la recursión sin copiar datos para crear nuevas sub-matrices para pasar recursivamente.
Otro ejemplo es una FFT que manipula recursivamente los datos comenzando con 2x2 "mariposas" y regresa a toda la matriz.
Para arreglar la matriz administrada, ahora necesita "algo más" para parchear este defecto y ese "algo más" se llama ''iteradores''. (Ahora ha gestionado matrices, pero casi nunca las pasa a ninguna función porque necesita iteradores más del 90% del tiempo).
Tienes razón en que alguna parte del sistema tendrá que saber algo sobre el tamaño.
Pero obtener esa información probablemente no esté cubierta por la API del sistema de administración de memoria (piense en
malloc
/
free
), y el tamaño exacto que solicitó puede no conocerse, ya que puede haberse redondeado.
Una razón fundamental es que no hay diferencia entre un puntero al primer elemento de una matriz de
T
asignada dinámicamente y un puntero a cualquier otra
T
Considere una función ficticia que devuelve el número de elementos a los que apunta un puntero.
Llamémoslo "tamaño".
Suena muy bien, ¿verdad?
Si no fuera por el hecho de que todos los punteros se crean de la misma manera:
char* p = new char[10];
size_t ps = size(p+1); // What?
char a[10] = {0};
size_t as = size(a); // Hmm...
size_t bs = size(a + 1); // Wut?
char i = 0;
size_t is = size(&i); // OK?
Se podría argumentar que el primero debe ser
9
, el segundo
10
, el tercero
9
y el último
1
, pero para lograr esto debe agregar una "etiqueta de tamaño" en
cada objeto
.
Un
char
requerirá 128 bits de almacenamiento (debido a la alineación) en una máquina de 64 bits.
Esto es dieciséis veces más de lo necesario.
(Arriba, la matriz de diez caracteres
a
requeriría al menos 168 bytes).
Esto puede ser conveniente, pero también es inaceptablemente caro.
Por supuesto, podría imaginar una versión que solo esté bien definida si el argumento realmente es un puntero al primer elemento de una asignación dinámica por parte del
operator new
predeterminado
operator new
, pero esto no es tan útil como podría pensarse.