c++: ¿Qué es la conversión de matriz a puntero? ¿decadencia?

CorePress2024-01-25  10

¿Qué es la conversión de matriz a puntero? ¿decadencia? ¿Existe alguna relación con los punteros de matriz?

85

poco conocido: el operador unario más se puede utilizar como "operador de caída": Dado int a[10]; int b(void);, entonces +a es un puntero int y +b es un puntero de función. Útil si quieres pasarlo a una plantilla que acepte una referencia.

- Johannes Schaub - litb

22 de septiembre de 2009 a las 18:36

3

@litb - los padres harían lo mismo (por ejemplo, (a) debería ser una expresión que se evalúe como un puntero), ¿verdad?.

- Michael Burr

22/09/2009 a las 19:30

27

std::decay de C++14 sería una forma menos oscura de descomponer una matriz sobre unario +.

- legends2k

25/03/2015 a las 6:43

30

@JohannesSchaub-litb dado que esta pregunta está etiquetada tanto en C como en C++, me gustaría aclarar que aunque +a y +b son legales en C++, es ilegal en C (C11 6.5.3.3/1 " ;El operando del operador unario + o - deberá ser de tipo aritmético")

- M.M.

31 de mayo de 2015 a las 13:41

5

@lege Correcto. Pero supongo que no es tan poco conocido como el trick con unario +. La razón por la que lo mencioné no fue simplemente porque se descompone sino porque es divertido jugar con él ;)

- Johannes Schaub - litb

31 de mayo de 2015 a las 16:54



------------------------------------

Se dice que las matrices "se descomponen" en punteros. Una matriz C++ declarada como números int [5] no se puede volver a apuntar, es decir, no se puede decir números = 0x5a5aff23. Más importante aún, el término decadencia significa pérdida de tipo y dimensión; los números se descomponen en int* al perder la información de dimensión (cuenta 5) y el tipo ya no es int [5]. Busque aquí los casos en los que no se produce la caries.

Si estás pasando una matriz por valor, ¿quéLo que realmente estamos haciendo es copiar un puntero: un puntero al primer elemento de la matriz se copia al parámetro (cuyo tipo también debe ser un puntero al tipo del elemento de la matriz). Esto funciona debido a la naturaleza en descomposición de la matriz; una vez descompuesto, sizeof ya no proporciona el tamaño completo de la matriz, porque esencialmente se convierte en un puntero. Por eso se prefiere (entre otras razones) pasar por referencia o puntero.

Tres formas de pasar una matriz1:

void by_value(const T* array)   // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])

Los dos últimos proporcionarán información sobre el tamaño adecuado, mientras que el primero no, ya que el argumento de la matriz ha decaído para ser asignado al parámetro.

1 La constante U debe conocerse en tiempo de compilación.

13

10

¿Cómo es el primer paso por valor?

- rlbond

22 de septiembre de 2009 a las 18:54

12

by_value pasa un puntero al primer elemento de la matriz; en el contexto de los parámetros de función, T a[] es idéntico a T *a. by_pointer pasa lo mismo, excepto que el valor del puntero ahora está calificado como constante. Si desea pasar un puntero a la matriz (a diferencia de un puntero al primer elementoent de la matriz), la sintaxis es T (*matriz)[U].

- John Bode

22/09/2009 a las 19:35

4

"con un puntero explícito a esa matriz" - Esto es incorrecto. Si a es una matriz de caracteres, entonces a es de tipo char[N] y se desintegrará a char*; pero &a es de tipo char(*)[N] y no decaerá.

- Pavel Minaev

22/09/2009 a las 19:39

6

@FredOverflow: Entonces, si U cambia, no tienes que acordarte de cambiarlo en dos lugares, o arriesgarte a errores silenciosos... ¡Autonomía!

-Carreras de ligereza en órbita

5 junio 2014 a las 10:40

5

"Si estás pasando una matriz por valor, lo que realmente estás haciendo es copiar un puntero" Eso no tiene sentido, porque las matrices no se pueden pasar por valor, punto.

-juanchopanza

20/09/2015 a las 13:38



------------------------------------

Las matrices son básicamente iguales que los punteros en C/C++, pero no del todo. Una vez que conviertes una matriz:

const int a[] = { 2, 3, 5, 7, 11 };

en un puntero (que funciona sin conversión y, por lo tanto, puede ocurrir inesperadamente en algunos casos):

const int* p = a;

pierdes la capacidad del operador sizeof de contar elementos en la matriz:

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

Esta capacidad perdida se conoce como "decadencia".

Para obtener más detalles, consulte este artículo sobre la descomposición de matrices.

4

61

Las matrices no son básicamente lo mismo que los punteros; son animales completamente diferentes. En la mayoría de los contextos, una matriz se puede tratar como si fuera un puntero y un puntero se puede tratar como si fuera una matriz, pero eso es lo más parecido que hay.

- John Bode

22/09/2009 a las 19:39

26

@John, perdona mi lenguaje impreciso. Estaba tratando de llegar a la respuesta sin empantanarme en una larga historia de fondo, y "ba"Sí, pero no del todo". Es la mejor explicación que he recibido en la universidad. Estoy seguro de que cualquiera que esté interesado podrá obtener una imagen más precisa de tu comentario con voto positivo.

PAUSA del sistema

23 de septiembre de 2009 a las 15:17

1

"funciona sin casting" significa lo mismo que "suceder implícitamente" cuando se habla de conversiones de tipos

- M.M.

17 de marzo de 2015 a las 3:39

2

El hecho de que la variable de matriz funcione casi como un puntero no significa necesariamente que sean lo mismo. Tienen diferentes tipos. Es por eso que el operador sizeof funciona en una matriz, no en un puntero que apunta a una matriz, aunque ambos tienen la misma dirección.

- Wu Xiliang

14 de enero de 2022 a las 2:10



------------------------------------

Esto es lo que dice el estándar (C99 6.3.2.1/3 - Otros operandos - Valores L, matrices y designadores de funciones):

Excepto cuando es el operando del operador sizeof o del operador unario & operador, o es un cadenaing literal utilizado para inicializar una matriz, una expresión que tiene el tipo "matriz de tipo" es convertido a una expresión con tipo "puntero a tipo" que apunta al elemento inicial de el objeto de matriz y no es un valor l.

Esto significa que prácticamente cada vez que se utiliza el nombre de la matriz en una expresión, se convierte automáticamente en un puntero al primer elemento de la matriz.

Tenga en cuenta que los nombres de funciones actúan de manera similar, pero los punteros de función se usan mucho menos y de una manera mucho más especializada que no causa tanta confusión como la conversión automática de nombres de matrices en punteros.

El estándar C++ (4.2 Conversión de matriz a puntero) flexibiliza el requisito de conversión a (el énfasis es mío):

Un valor l o rvalue de tipo “matriz de N T” o “matriz delímite desconocido de T” se puede convertir a un valor r de tipo “puntero a T.”

Por lo tanto, la conversión no tiene que ocurrir como ocurre casi siempre en C (esto permite que las funciones se sobrecarguen o que las plantillas coincidan en el tipo de matriz).

Esta es también la razón por la que en C debes evitar el uso de parámetros de matriz en prototipos/definiciones de funciones (en mi opinión, no estoy seguro de si existe algún acuerdo general). Causan confusión y son una ficción de todos modos: use parámetros de puntero y es posible que la confusión no desaparezca por completo, pero al menos la declaración del parámetro no miente.

2

2

¿Qué es una línea de código de ejemplo donde se muestra una "expresión que tiene el tipo 'matriz de tipos'"? ¿Se utiliza "una cadena literal para inicializar una matriz"?

- Garrett

8 de julio de 2014 a las 2:45

5

@Garrett char x[] = "Hola"; . La matriz de 6 elementos "Hola" no se pudre; en lugar de eso, x obtiene el tamaño 6 y sus elementos se inicializan a partir de los elementos de "Hola".

- M.M.

17/03/2015 a las 3:40



------------------------------------

"Decadencia" se refiere a la conversión implícita de una expresión de un tipo de matriz a un tipo de puntero. En la mayoría de los contextos, cuando el compilador ve una expresión de matriz, convierte el tipo de expresión de "matriz de N elementos de T" a "puntero a T" y establece el valor de la expresión en la dirección del primer elemento de la matriz. Las excepciones a esta regla se dan cuando una matriz es un operando del tamaño de o & operadores, o la matriz es unaliteral de cadena que se utiliza como inicializador en una declaración.

Suponga el siguiente código:

char a[80];
strcpy(a, "This is a test");

La expresión a es del tipo "matriz de caracteres de 80 elementos" y la expresión "Esto es una prueba" es del tipo "matriz de caracteres de 15 elementos" (en C; en C++ los literales de cadena son matrices de caracteres constantes). Sin embargo, en la llamada a strcpy(), ninguna expresión es un operando de sizeof o &, por lo que sus tipos se convierten implícitamente en "puntero a char" y sus valores se establecen en la dirección del primer elemento de cada uno. Lo que recibe strcpy() no son arrays, sino punteros, como se ve en su prototipo:

char *strcpy(char *dest, const char *src);

Esto no es lo mismo que un puntero de matriz. Por ejemplo:

char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;

Tanto ptr_to_first_element como ptr_to_array tienen el mismo valor; el base dirección de a. Sin embargo, son tipos diferentes y se tratan de manera diferente, como se muestra a continuación:

a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]

Recuerde que la expresión a[i] se interpreta como *(a+i) (que solo funciona si el tipo de matriz se convierte a un tipo de puntero), por lo que tanto a[i] como ptr_to_first_element[i] funcionan igual . La expresión (*ptr_to_array)[i] se interpreta como *(*a+i). Las expresiones *ptr_to_array[i] y ptr_to_array[i] pueden provocar advertencias o errores del compilador según el contexto; definitivamente harán algo incorrecto si esperas que evalúen como [i].

sizeof a == sizeof *ptr_to_array == 80

Nuevamente, cuando una matriz es un operando de sizeof, no se convierte a un tipo de puntero.

sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
                                                  is on your platform

ptr_to_first_element es un puntero simple a char.

Respondido

22 de septiembre de 2009 a las 20:21

Juan Bode

Juan Bode

121k

19

19 insignias de oro

124

124 insignias de plata

201

201 insignias de bronce

2

2

¿No es "Esto es una prueba"? es del tipo "matriz de caracteres de 16 elementos" ¿una "matriz de caracteres de 15 elementos"? (longitud 14 + 1 para \0)

– chux - Reincorporar a Mónica

7 diciembre 2013 a las 17:47

Tenga en cuenta que *ptr_to_array se evalúa como el valor de la matriz, que luego se descompone inmediatamente en un puntero al primer elemento de la matriz, a menos que sea el operando de sizeof o unario &

- Chris Dodd

27/09/2021 a las 19:17



------------------------------------

Las matrices, en C, no tienen valor.

Donde se espera el valor de un objeto pero el objeto es una matriz, la dirección de su primer elemento se utiliza enen su lugar, con puntero de tipo a (tipo de elementos de la matriz).

En una función, todos los parámetros se pasan por valor (las matrices no son una excepción). Cuando pasas una matriz en una función, "se descompone en un puntero" (sic); cuando comparas una matriz con otra cosa, nuevamente "se descompone en un puntero" (sic); ...

void foo(int arr[]);

La función foo espera el valor de una matriz. ¡Pero, en C, las matrices no tienen valor! Entonces foo obtiene en su lugar la dirección del primer elemento de la matriz.

int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }

En la comparación anterior, arr no tiene valor, por lo que se convierte en un puntero. Se convierte en un puntero a int. Ese puntero se puede comparar con la variable ip.

En la sintaxis de indexación de matrices que estás acostumbrado a ver, nuevamente, el arr se 'descompone en un puntero'

arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */

Las únicas ocasiones en las que una matriz no se descompone en un puntero soncuando es el operando del operador sizeof, o el & operador (el operador 'dirección de'), o como una cadena literal utilizada para inicializar una matriz de caracteres.

12

7

"Las matrices no tienen valor" - ¿Qué se supone que significa eso? Por supuesto, las matrices tienen valor... son objetos, puedes tener punteros y, en C++, referencias a ellos, etc.

- Pavel Minaev

22/09/2009 a las 19:40

2

Creo estrictamente que el "Valor" se define en C como la interpretación de los bits de un objeto según un tipo. Me cuesta encontrar un significado útil para eso con un tipo de matriz. En su lugar, puede decir que convierte a un puntero, pero eso no interpreta el contenido de la matriz, solo obtiene su ubicación. Lo que obtienes es el valor de un puntero (y es una dirección), no el valor de una matriz (esto sería "la secuencia de valores de los elementos contenidos", como se usa en la definición de "un puntero"). cadena"). Dicho esto, creo que es justo decir "valor de la matriz" cuando uno se refiere al puntero uno gets.

- Johannes Schaub - litb

22 de septiembre de 2009 a las 19:56

De todos modos, creo que hay una ligera ambigüedad: valor de un objeto y valor de una expresión (como en "rvalue"). Si se interpreta de esta última manera, entonces una expresión de matriz seguramente tiene un valor: es el que resulta de desintegrarlo a un valor r, y es la expresión de puntero. Pero si se interpreta de la forma anterior, entonces, por supuesto, no hay ningún significado útil para un objeto de matriz.

- Johannes Schaub - litb

22 de septiembre de 2009 a las 20:01

1

+1 para la frase con una pequeña corrección; para las matrices ni siquiera es un triplete, solo un pareado [ubicación, tipo]. ¿Tenías algo más en mente para la tercera ubicación en el caso del array? No se me ocurre ninguno.

- legends2k

8/07/2014 a las 14:50

1

@legends2k: Creo que usé el tercero.ubicación en matrices para evitar que sean un caso especial de tener solo un pareado. Quizás [ubicación, tipo, vacío] hubiera sido mejor.

- pmg

8/07/2014 a las 15:55



------------------------------------

Es cuando el array se pudre y está siendo apuntado ;-)

En realidad, es sólo que si quieres pasar una matriz a algún lugar, pero en su lugar se pasa el puntero (porque quién diablos pasaría toda la matriz por ti), la gente dice que esa matriz pobre se descompuso en un puntero.

4

Bien dicho. ¿Cuál sería una buena matriz que no se descomponga en un puntero o una que no se descomponga? ¿Puedes citar un ejemplo en C? Gracias.

- Unheilig

10 de enero de 2014 a las 5:20

@Unheilig, claro, se puede empaquetar al vacío una matriz en una estructura y pasar la estructura.

-Michael Krelin - hacker

10 de enero de 2014 a las 9:13

No estoy seguro de qué quieres decir con "trabajo". No está permitido acceder más allá de la matriz, aunque funciona como se esperaba si esperas lo que realmente sucederá. Ese comportamiento (aunque, nuevamente, oficialmente no definido) se conserva.

-Michael Krelin - hacker

10/01/2014 a las 20:08

La decadencia también ocurre en muchas situaciones en las que la matriz no pasa a ninguna parte (como se describe en otras respuestas). Por ejemplo, un + 1.

- M.M.

17 de marzo de 2015 a las 3:42



------------------------------------

La decadencia de una matriz significa que, cuando una matriz se pasa como parámetro a una función, se trata de manera idéntica ("decae") a un puntero.

void do_something(int *array) {
  // We don't know how big array is here, because it's decayed to a pointer.
  printf("%i\n", sizeof(array));  // always prints 4 on a 32-bit machine
}

int main (int argc, char **argv) {
    int a[10];
    int b[20];
    int *c;
    printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
    printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
    printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
    do_something(a);
    do_something(b);
    do_something(c);
}

Hay dos complicaciones o excepciones a lo anterior.

En primer lugar, cuando se trata de matrices multidimensionales en C y C++, sólo se pierde la primera dimensión. Esto se debe a que las matrices se distribuyen de forma contigua en la memoria, por lo que el compilador debe conocer todas las dimensiones excepto la primera para poder calcular las compensaciones en ese bloque de memoria.

void do_something(int array[][10])
{
    // We don't know how big the first dimension is.
}

int main(int argc, char *argv[]) {
    int a[5][10];
    int b[20][10];
    do_something(a);
    do_something(b);
    return 0;
}

En segundo lugar, en C++, puedes usar plantillas para deducir el tamaño de las matrices. microsoftusa esto para las versiones C++ de funciones Secure CRT como strcpy_s, y puedes usar un truco similar para obtener de manera confiable la cantidad de elementos en una matriz.

Respondido

22/09/2009 a las 17:35

Josh Kelley

Josh Kelley

56,9k

20

20 insignias de oro

150

150 insignias de plata

248

248 de bronce insignias

1

1

la decadencia ocurre en muchas otras situaciones, no solo al pasar una matriz a una función.

- M.M.

17 de marzo de 2015 a las 3:44



------------------------------------

Prueba este código


void f(double a[10]) {
    printf("in function: %d", sizeof(a));
    printf("pointer size: %d\n", sizeof(double *));
}

int main() {
    double a[10];
    printf("in main: %d", sizeof(a));
    f(a);
}

y verás que el tamaño de la matriz dentro de la función no es igual al tamaño de la matriz en main, pero sí es igual al tamaño de un puntero.

Probablemente hayas oído que "las matrices son punteros", pero esto no es exactamente cierto (el tamaño del interior de main imprime el tamaño correcto). Sin embargo, cuando se pasa, la matriz decae hasta convertirse en un puntero. Es decir, independientemente de lo que muestre la sintaxis, en realidad pasas un puntero,y la función realmente recibe un puntero.

En este caso, la definición void f(double a[10] es transformada implícitamente por el compilador a void f(double *a). De manera equivalente, podría haber declarado el argumento de la función directamente como *a. Incluso podría haber escrito a[100] o a[1], en lugar de a[10], ya que en realidad nunca se compila de esa manera (sin embargo, obviamente no deberías hacerlo, confundiría al lector).



------------------------------------

tl;dr: Cuando usas una matriz que has definido, en realidad estarás usando un puntero a su primer elemento.

Así:

Cuando escribes arr[idx] en realidad solo estás diciendoing *(arr + idx). Las funciones nunca toman matrices como parámetros, solo punteros, ya sea directamente, cuando especifica un parámetro de matriz, o indirectamente, si pasa una referencia a una matriz.

Tipos de excepciones a esta regla:

Puede pasar matrices de longitud fija a funciones dentro de una estructura. sizeof() proporciona el tamaño que ocupa la matriz, no el tamaño de un puntero.

3

las matrices se pueden pasar por referencia a funciones. Y no entiendo cómo sizeof dar el tamaño de la matriz en lugar del puntero es una excepción a funcciones que no toman matrices como parámetros. El problema común es que sizeof devuelve el tamaño de un puntero cuando se usa en un puntero que se origina al pasar una matriz a una función

-463035818_is_not_an_ai

25/03/2021 a las 10:35

1

@largest_prime_is_463035818: Mi TL;DR habló sobre el uso de una matriz en general, no solo sobre pasarla a una función. Además, editado para aclarar que puede pasar una matriz por referencia.

- einpoklum

25/03/2021 a las 11:30

Gracias, entendido. "Una especie de excepción" se refiere a la primera línea, no a la frase "Así". como lo leí mal por primera vez

-463035818_is_not_an_ai

25/03/2021 a las 11:34



------------------------------------

Me atrevería a pensar que hay cuatro (4) formas de pasar una matriz como argumento de una función. También aquí está el código breve pero funcional para su lectura.

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

using namespace std;

// test data
// notice native array init with no copy aka "="
// not possible in C
 const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };

// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) { 
    // a pointer
    assert(array != nullptr); 
} ;

// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) { 
    // decayed to a pointer
    assert( array != nullptr ); 
}

// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
   // dealing with native pointer 
    assert( array != nullptr ); 
}

// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
    // array is not a pointer here
    // it is (almost) a container
    // most of the std:: lib algorithms 
    // do work on array reference, for example
    // range for requires std::begin() and std::end()
    // on the type passed as range to iterate over
    for (auto && elem : array )
    {
        cout << endl << elem ;
    }
}

int main()
{
     // ONE
     as_pointer(specimen);
     // TWO
     by_value_no_size(specimen);
     // THREE
     pointer_to_array(&specimen);
     // FOUR
     reference_to_array( specimen ) ;
}

También podría pensar que esto muestra la superioridad de C++ frente a C. Al menos en referencia (juego de palabras) de pasar una matriz por referencia.

De cPor lo tanto, existen proyectos extremadamente estrictos sin asignación de montón, sin excepciones y sin std:: lib. Se podría decir que el manejo de matrices nativas de C++ es una característica del lenguaje de misión crítica.



------------------------------------

Las matrices se pasan automáticamente mediante un puntero en C. La razón detrás de esto sólo puede especularse.

int a[5], int *a e int (*a)[5] son ​​todas direcciones glorificadas, lo que significa que el compilador trata a los operadores aritméticos y de deferencia de manera diferente según el tipo, por lo que cuando se refieren a la misma dirección el compilador no los trata de la misma manera. int a[5] es diferente a los otros 2 enque la dirección es implícita y no se manifiesta en la pila o en el ejecutable como parte de la matriz misma, solo la usa el compilador para resolver ciertas operaciones aritméticas, como tomar su dirección o aritmética de punteros. int a[5] es, por lo tanto, una matriz y también una dirección implícita, pero tan pronto como hablas de la dirección en sí y la colocas en la pila, la dirección en sí ya no es una matriz y solo puede ser un puntero a una matriz o una matriz descompuesta, es decir, un puntero al primer miembro de la matriz.

Por ejemplo, en int (*a)[5], la primera desreferencia en a producirá un int * (por lo tanto, la misma dirección, solo que un tipo diferente, y tenga en cuenta que no es int a[5]) y aritmética de punteros. en a, es decir, a+1 o *(a+1) estará en términos del tamaño de una matriz de 5 entradas (que es el tipo de datos queapunta a), y la segunda desreferencia producirá el int. Sin embargo, en int a[5], la primera desreferencia producirá el int y la aritmética del puntero será en términos del tamaño de un int.

A una función, sólo puede pasar int * e int (*)[5], y la función lo convierte en cualquier tipo de parámetro, por lo que dentro de la función tiene la opción de tratar una dirección que está siendo se pasa como una matriz descompuesta o un puntero a una matriz (donde la función tiene que especificar el tamaño de la matriz que se pasa). Si pasa a a una función y a se define int a[5], entonces, cuando se resuelve en una dirección, está pasando una dirección, y una dirección solo puede ser un tipo de puntero. En la función, el parámetro al que accede es entonces una dirección en la pila o en un registro, que sólo puede ser una punta.tipo r y no un tipo de matriz; esto se debe a que es una dirección real en la pila y, por lo tanto, claramente no es la matriz en sí.

Se pierde el tamaño del array porque el tipo del parámetro al ser una dirección es un puntero y no un array, que no tiene tamaño de array, como se puede ver al usar sizeof, que trabaja sobre el tipo del valor que se le pasa. El tipo de parámetro int a[5] en lugar de int *a está permitido, pero se trata como int * en lugar de prohibirlo por completo, aunque debería prohibirse porque es engañoso, porque hace pensar que se puede usar la información de tamaño. , pero solo puedes hacer esto convirtiéndolo a int (*a)[5] y, por supuesto, la función tiene que especificar el tamaño de la matriz porque no hay forma de pasar el tamaño de la matriz porqueEl tamaño de la matriz debe ser una constante en tiempo de compilación.

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare