c++ - una - matrices en programacion
Inicializar todos los elementos de una matriz al mismo nĂºmero. (7)
Hace algún tiempo, mi antiguo maestro publicó este código diciendo que es otra forma de inicializar una matriz al mismo número (que no sea cero, por supuesto).
Tres en este caso.
Dijo que de esta manera es un poco mejor que el bucle
for
.
¿Por qué necesito el operador de desplazamiento a la izquierda?
¿Por qué necesito otra serie de largos?
No entiendo nada de lo que está pasando aquí.
int main() {
short int A[100];
long int v = 3;
v = (v << 16) + 3;
v = (v << 16) + 3;
v = (v << 16) + 3;
long *B = (long*)A;
for(int i=0; i<25; i++)
B[i] = v;
cout << endl;
print(A,100);
}
Como se explica en otras respuestas, el código viola las reglas de alias de tipo y hace suposiciones que no están garantizadas por el estándar.
Si realmente quisiera hacer esta optimización a mano, esta sería una forma correcta de tener un comportamiento bien definido:
long v;
for(int i=0; i < sizeof v / sizeof *A; i++) {
v = (v << sizeof *A * CHAR_BIT) + 3;
}
for(int i=0; i < sizeof A / sizeof v; i++) {
std:memcpy(A + i * sizeof v, &v, sizeof v);
}
Las suposiciones inseguras sobre los tamaños de los objetos se corrigieron mediante el uso de
sizeof
, y la violación de alias se solucionó usando
std::memcpy
, que tiene un comportamiento bien definido independientemente del tipo subyacente.
Dicho esto, probablemente sea mejor mantener el código simple y dejar que el compilador haga su magia.
¿Por qué necesito el operador de desplazamiento a la izquierda?
El punto es llenar un número entero más grande con múltiples copias del número entero más pequeño.
Si escribe un valor de dos bytes
s
en un número entero grande
l
, luego
cambia
los bits a la izquierda por dos bytes (mi versión fija debería ser más clara acerca de dónde provienen esos números mágicos), entonces tendrá un número entero con dos copias de bytes que constituyen el valor
s
.
Esto se repite hasta que todos los pares de bytes en
l
estén configurados con esos mismos valores.
Para hacer el turno, necesita el operador de turno.
Cuando esos valores se copian sobre una matriz que contiene una matriz de enteros de dos bytes, una sola copia establecerá el valor de varios objetos en el valor de los bytes del objeto más grande. Como cada par de bytes tiene el mismo valor, también lo tendrán los enteros más pequeños de la matriz.
¿Por qué necesito otra serie de
long
?
No hay matrices de
long
.
Solo una serie de
short
.
Creo que está tratando de reducir el número de iteraciones de bucle copiando múltiples elementos de matriz al mismo tiempo. Como otros usuarios ya mencionaron aquí, esta lógica conduciría a un comportamiento indefinido.
Si se trata de reducir las iteraciones, entonces con el ciclo de desenrollado podemos reducir el número de iteraciones. Pero no será significativamente más rápido para matrices tan pequeñas.
int main() {
short int A[100];
for(int i=0; i<100; i+=4)
{
A[i] = 3;
A[i + 1] = 3;
A[i + 2] = 3;
A[i + 3] = 3;
}
print(A, 100);
}
El código que su maestro le ha mostrado es un programa mal formado, no se requiere diagnóstico, porque viola el requisito de que los punteros realmente señalen lo que dicen señalar (también conocido como "alias estricto").
Como ejemplo concreto, un compilador puede analizar su programa, notar que
A
no se escribió directamente y que no se escribió ningún
short
, y probar que
A
nunca se cambió una vez creado.
Se puede demostrar que todos esos problemas con
B
, bajo el estándar C ++, no pueden modificar
A
en un programa bien formado.
Es probable que un bucle
for(;;)
o incluso un rango forzado se optimice hasta la inicialización estática de
A
El código de su maestro, bajo un compilador optimizador, se optimizará a un comportamiento indefinido.
Si realmente necesita una forma de crear una matriz inicializada con un valor, puede usar esto:
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>) {
return [](auto&&f)->decltype(auto) {
return f( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={})
{
return index_over( std::make_index_sequence<N>{} );
}
template<class T, std::size_t N, T value>
std::array<T, N> make_filled_array() {
return index_upto<N>()( [](auto...Is)->std::array<T,N>{
return {{ (void(Is),value)... }};
});
}
y ahora:
int main() {
auto A = make_filled_array<short, 100, 3>();
std::cout << "/n";
print(A.data(),100);
}
crea la matriz llena en tiempo de compilación, sin bucles involucrados.
Usando godbolt puede ver que el valor de la matriz se calculó en tiempo de compilación, y el valor 3 se extrajo cuando accedo al elemento 50.
Esto es, sin embargo, exagerado (y c ++ 14 ).
Hay muchas formas de llenar una matriz con el mismo valor, y si le preocupa el rendimiento, entonces necesita medir.
C ++ tiene una función dedicada para llenar una matriz con un valor, y usaría esto (después de
#include <algorithm>
y
#include <iterator>
):
std::fill(std::begin(A), std::end(A), 3);
No debe subestimar lo que los compiladores de optimización pueden hacer con algo como esto.
Si está interesado en ver lo que hace el compilador, entonces el
Compiler Explorer de
Matt Godbolt es una muy buena herramienta si está preparado para aprender un poco de ensamblador.
Como puede ver desde
here
, los compiladores pueden optimizar la llamada de
fill
a doce (y un poco) tiendas de 128 bits con cualquier bucle desenrollado.
Debido a que los compiladores tienen conocimiento del entorno de destino, pueden hacerlo sin codificar ninguna suposición específica del objetivo en el código fuente.
La pregunta tiene la etiqueta C ++ (sin etiqueta C), por lo que esto debe hacerse en estilo C ++:
// C++ 03
std::vector<int> tab(100, 3);
// C++ 11
auto tab = std::vector<int>(100, 3);
auto tab2 = std::array<int, 100>{};
tab2.fill(3);
Además, el profesor está intentando burlar al compilador que puede hacer cosas alucinantes. No tiene sentido hacer tales trucos ya que el compilador puede hacerlo por usted si está configurado correctamente:
- Sus ensambles de código
- Su código ensambla con la marca eliminada
- Enfoque de matriz
- Enfoque vectorial
Como puede ver, el código de resultado
-O2
es (casi) el mismo para cada versión.
En el caso de
-O1
, los trucos mejoran un poco.
Entonces, en resumen, tienes que elegir:
- Escriba código difícil de leer y no use optimizaciones del compilador
-
Escriba código legible y use
-O2
Use el sitio de Godbolt para experimentar con otros compiladores y configuraciones. Vea también la última charla de cppCon .
Qué absoluta carga de basura.
-
Para empezar,
v
se calculará en tiempo de compilación . -
El comportamiento de desreferenciar
B
después delong *B = (long*)A;
no está definido ya que los tipos no están relacionados.B[i]
es una desreferencia deB
-
No hay justificación alguna para suponer que un
long
es cuatro veces más grande que unshort
.
Utilice un bucle
for
forma sencilla y confíe en el compilador para optimizar.
Bastante por favor, con azúcar encima.
Supone que
long
es cuatro veces más largo que
short
(eso no está garantizado; debería usar int16_t e int64_t).
Toma ese espacio de memoria más largo (64 bits) y lo llena con cuatro valores cortos (16 bits). Está configurando los valores cambiando bits por 16 espacios.
Luego quiere tratar un conjunto de cortos como un conjunto de largos, para poder establecer 100 valores de 16 bits haciendo solo 25 iteraciones de bucle en lugar de 100.
Esa es la forma en que piensa su maestro, pero como otros dijeron, este elenco es un comportamiento indefinido.