Rompiendo la pared

Tercer problema, esta vez un poco de C++, y además muy fácil. Este es un problema clásico de mi carpeta de problemas a plantear en entrevistas de trabajo (yo personalmente nunca contrataría a alguien que no supiera resolverlo).

Este problema vino inspirado cuando, una vez, escuché a una persona A decirle a una persona B que no había forma de acceder a un miembro privado de una clase sin un getter, sin ser friend, etc…

Vamos a demostrarle todos a A que sí se puede:

class Locked
{
   public:
      int number;
   private:
      int secret;
   public:
      Locked() {number=-7;secret=85;}
};

int main()
{
   Locked black_box;
   int x = /* solución aquí */ ;
}

Tenemos un clase “Locked” con un entero público (number) y otro privado (secret). Tenemos que guardar en la variable “x” el valor del entero “secret” de la instancia “black_box” de la clase “Locked”. La solución cabe en una línea y por supuesto no puede escribirse nada fuera del main.

¿Voy abriendo ya la cerveza? 😀


Si te gusta esta entrada... compártela! 😉
Tweet about this on TwitterShare on Facebook0Share on Google+0Share on Tumblr0Pin on Pinterest0Share on LinkedIn0Share on Reddit0

5 Comentarios

  1. StormByte

    Usando la aritmética de punteros es posible acceder a este tipo de cosas a bajo nivel, puesto que, internamente el compilador, almacena estos datos en memoria como si de un struct se tratase.

    Con lo que el resultado sería tan “sencillo” como:

    int x = *((int*)(&black_box)+1);

    Sin embargo, a pesar de que es posible hacer esto, he de añadir que es una práctica muy insegura, que hará que nuestro software sea propenso a tener incompatibilidades, por ejemplo, cuando se actualice el compilador.

    Como nota para quien tenga curiosidad, diré que es, justamente por hacer cosas así, por las que la mayoría de programas de Windows, dejan de funcionar de versión a versión, porque, dado que hay muchas funciones sin documentar, en ocasiones, se accede a datos en arrays y objetos de propiedades de Windows de esta forma, y, cuando cambia la versión y el compilador alinea los datos de otra forma, dichos programas fallan.

    • StormByte

      NOTA 2: Este tipo de trucos no se puede aplicar a miembros estáticos, puesto que, al no formar parte de una instancia en concreto, no se almacenan de la misma forma en memoria.

      NOTA 3: Para reflejar este tipo de asignaciones de memoria por si alguien tiene curiosidad, he modificado la clase para ponerle getters con direcciones de memoria. Este es el resultado:

      #include <iostream>
      
      class Locked
      {
         public:
            int number;
         private:
            int secret;
            static int secret2;
         public:
            Locked() {number=-7;secret=85;}
            int* GetAddrNumber() { return &number; }
            int* GetAddrSecret() { return &secret; }
            static int* GetAddrSecret2() { return &secret2; } 
      };
      
      int Locked::secret2=99;
      int main()
      {
         Locked black_box;
         std::cout << &black_box << std::endl;
         std::cout << black_box.GetAddrNumber() << std::endl;
         std::cout << black_box.GetAddrSecret() << std::endl;
         std::cout << black_box.GetAddrSecret2() << std::endl;
         int x = *((int*)(&black_box)+1);
         std::cout << x << std::endl;
      }
      

      La salida es la siguiente:
      0x7fff83c059b0
      0x7fff83c059b0
      0x7fff83c059b4
      0x602080
      85

      Como veis, el entero público tiene la misma dirección de memoria que el objeto en sí, puesto que se almacena en primera posición, seguido de la variable privada a 4 bytes de distancia (en mi arquitectura 64bits, int=4 bytes), y se puede apreciar tambien, como el dato estático, tiene una dirección de memoria no correlativa, por lo que este "truco" no se podría aplicar.

      Gracias Lanshor por el problema 😀

    • LaNsHoR

      Y ya tenemos un ganador!! 🙂 //te debo una cerveza 😀

      Aclarar que, si los compiladores siguen el estándar ansi-c++, siempre funcionará este código con total seguridad.

      El estándar garantiza que la estructura en memoria de los objetos siguen, como en C, los criterios POD (“Plain Old Data“).

      • StormByte

        Si, exactamente, por lo menos el gcc lo cumple, aunque el que usa Microsoft para Windows no, y alinea como mejor le conviene (de ahí la nota anterior).

        También quiero añadir que, en algunos casos, el estandard (o la arquitectura) obliga a tener cierto padding en los datos, por lo que el truco podría no funcionar, por ejemplo en el caso de tener 2 int y un char, si no se especifica el atributo packed (que sólo funcionará en x86 ó amd64), el tamaño final no será el esperado y tendremos que tener eso en cuenta a la hora de acceder a los datos de esa manera 🙂

        • LaNsHoR

          Efectivamente, muchos compiladores rompen la especificación para alinear los datos en memoria. Dado que el bus de datos de la placa es de 32/64 bits, un entero que empiece en una posición no múltiplo de 4 requeriría dos lecturas para llegar a CPU. Con la enorme degradación de rendimiento que esto supone.

          datos no alineados

Responder a LaNsHoR Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Límite de tiempo se agote. Por favor, recargar el CAPTCHA por favor.