3

Aprendí el lenguaje Python y ahora me estoy mudando a C++ por la altísima velocidad de este lenguaje.

Basándome en la documentación de https://www.cplusplus.com/reference/ctime/clock/ he hecho un pequeño programa de prueba que funciona bien y es el siguiente:

#include <cstdint>
#include <cstdlib>
#include <ctime>

/*este código crea una matriz lineal 1D y la rellena con números aleatorios
que pertenezcan al rango 0-255. El objetivo es medir el tiempo que le toma
completar la tarea*/


int main (){
    
    //configuro una semilla aleario usando el tiempo
    std::srand(std::time(NULL));
    
    //tamaño de la matriz 1D
    int size=800*600;
    
    //capturamos el tiempo para la medición
    std::clock_t timecounter = std::clock();

    
    
    //creamos la matriz lineal de tipo uint8_t
    std::uint8_t pixeles[1300*800];
    
    //la rellenamos con números aleatorios
    for (int k=0; k<size; k++){
        pixeles[k]=std::rand()%256;
    }
    
    //capturamos el tiempo de nuevo y le restamos el registro anterior 
    //para obtener el lapso transcurrido entre las dos llamadas
    double tiempointermedio=std::clock()-timecounter;
    
    //por ultimo mostramos el resultado en pantalla pero convertimos el
    //tiempo en segundos por medio de una división justo como se muestra en 
    //la documentación https://www.cplusplus.com/reference/ctime/clock/
    std::cout << "finalizaddo exitosamente en :" << tiempointermedio/CLOCKS_PER_SEC << std::endl;
}

La parte que no entiendo es muy absurda, según la documentación cada vez que #incluyo una cabecera todo su contenido se añaden al espacio de nombres std, pero por alguna razón que desconozco algunas variables no se añaden a ese espacio de nombres como es el caso de:

//forma correcta si se usa directamente
CLOCKS_PER_SEC

//error de compilación si se usa std
std::CLOCKS_PER_SEC

introducir la descripción de la imagen aquí

Si alguien es tan amable de explicarme esta duda súper absurda. GRACIAS

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82
JosephGen
  • 301
  • 1
  • 9

2 Answers2

2

según la documentación cada vez que #incluyo una cabecera todo su contenido se añaden al espacio de nombres std

Esa parte la estás entendiendo mal

Cuando tu estás escribiendo una parte del programa eres libre de elegir el espacio de nombres en el que se va a alojar dicho código. De hecho podrías decidir no incluirlo en ningún espacio de nombres:

void func()
{ std::cout << "func()"; }

namespace POO
{

    void func()
    { std::cout << "POO:func()"; }

}

int main()
{
    func();
    POO::func();
}

Ahora vamos a aparcar un momento este tema y vamos a hablar del proceso de compilación en C.

Para compilar un programa en C se ejecutan 3 tareas independientes:

  • Preprocesador
  • Compilación
  • Enlazado

El preprocesador es un programa que lee los diferentes archivos de nuestro programa en busca de directivas del preprocesador (esas instrucciones que empiezan con #). Cada vez que localiza alguna, reemplaza dicha directiva por otro contenido. Así, por ejemplo:

  • #include se reemplaza por el contenido del archivo al que hace referencia
  • #define crea una macro, la directiva desaparece
  • #ifdef, #else, #endif, ... son directivas de control. El código que está entre ellas puede desaparecer del programa final si no se cumplen las condiciones

El preprocesador no solo busca directivas como las mencionadas, también busca las macros que se han definido y las reemplaza por el código de la propia macro.

Este proceso es recursivo, es decir, cada vez que se hace un #include, el contenido del archivo incluido se pasa también por el preprocesador. El resultado final es un archivo generalmente grande que no contiene ninguna directiva de preprocesador ni ninguna macro, solo código C en estado puro.

Este código resultante es el que se pasa a la siguiente etapa, la etapa de compilación. En esta etapa se leen los diferentes archivos y se genera código objeto.

Finalmente le llega el turno al enlazador. El enlazador coge todos los códigos objeto y los une bajo un mismo ejecutable/librería.

Para hacernos una idea gráfica, el código objeto lo puedes equiparar a los retales que se usan para crear una funda. El enlazador sería el proceso de coger todos esos retales y unirlos para crear el producto final.

Vamos a verlo con un ejemplo:

A.h

#ifndef A_H
#define A_H

#include "B.h"

int funcA(int value)
{ return value * 2 - 1; }

#endif

B.h

#ifndef B_H
#define B_H

#include "A.h"

int funcB(int value)
{ return funcA(value + 10); }

#endif

main.c

#include "B.h"

#define P int main
#define R (
#define O ) { int var =
#define G funcB
#define A 123); std::cout << var;
#define M }

P R O G R A M

Al compilar main.c sucede lo siguiente:

Precompilador

  • encuentra #include "b.h", por lo que reemplaza esta directiva por el contenido del archivo

    #ifndef B_H
    #define B_H
    
    #include "A.h"
    
    int funcB(int value)
    { return funcA(value + 10); }
    
    #endif
    
    #define P int main
    #define R (
    #define O ) { int var =
    #define G funcB
    #define A 123); std::cout << var;
    #define M }
    
    P R O G R A M
    
  • Se procesa `#ifdef B_H. Como el síbolo no está definido, la guarda simplemente desaparece

    #define B_H
    
    #include "A.h"
    
    int funcB(int value)
    { return funcA(value + 10); }
    
    #define P int main
    #define R (
    #define O ) { int var =
    #define G funcB
    #define A 123); std::cout << var;
    #define M }
    
    P R O G R A M
    
  • Se procesaría #define B_H, este lo voy a dejar para más adelante por claridad

  • Se procesa #include "A.h"

    #define B_H
    
    #ifndef A_H
    #define A_H
    
    #include "B.h"
    
    int funcA(int value)
    { return value * 2 - 1; }
    
    #endif
    
    int funcB(int value)
    { return funcA(value + 10); }
    
    #define P int main
    #define R (
    #define O ) { int var =
    #define G funcB
    #define A 123); std::cout << var;
    #define M }
    
    P R O G R A M
    
  • Se procesa #ifdef A_H. El símbolo no está definido así que la guarda desaparece

    #define B_H
    
    #define A_H
    
    #include "B.h"
    
    int funcA(int value)
    { return value * 2 - 1; }
    
    int funcB(int value)
    { return funcA(value + 10); }
    
    #define P int main
    #define R (
    #define O ) { int var =
    #define G funcB
    #define A 123); std::cout << var;
    #define M }
    
    P R O G R A M
    
  • Se procesa `#include "B.h"

    #define B_H
    
    #define A_H
    
    #ifndef B_H
    #define B_H
    
    #include "A.h"
    
    int funcB(int value)
    { return funcA(value + 10); }
    
    #endif
    
    int funcA(int value)
    { return value * 2 - 1; }
    
    int funcB(int value)
    { return funcA(value + 10); }
    
    #define P int main
    #define R (
    #define O ) { int var =
    #define G funcB
    #define A 123); std::cout << var;
    #define M }
    
    P R O G R A M
    
  • Se procesa #ifdef B_H. Como se puede ver, este símbolo ya está definido en la primera línea. El resultado es que todo lo que se encuentre entre #ifdef y su correspondiente #endif desaparece

    #define B_H
    
    #define A_H
    
    int funcA(int value)
    { return value * 2 - 1; }
    
    int funcB(int value)
    { return funcA(value + 10); }
    
    #define P int main
    #define R (
    #define O ) { int var =
    #define G funcB
    #define A 123); std::cout << var;
    #define M }
    
    P R O G R A M
    
  • El preprocesador se apuntaría las diferentes macros y el código que las reemplaza

  • Finalmente, el procesador encuentra usos de las distintas macros y reemplaza dichos usos por el código correspondiente

    int funcA(int value)
    { return value * 2 - 1; }
    
    int funcB(int value)
    { return funcA(value + 10); }
    
      int main() { int var = funcB(123); std::cout << var; }
    //~~~~~~~~ ~~~~~~~~~~~~~      ~                         ~
    //        ~              ~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~
    //   P    R       O        G  R           A             M
    

El procesador ha terminado su tarea, ahora le llegaría el turno al compilador.

Puedes ver el ejemplo funcionando aquí

Como puedes ver, pese a que hemos hecho uso de #include, el código no ha acabado en el espacio de nombres std. Eso solo sucederá si el código, en el momento de escribir la cabecera, se ha incluído en ese espacio de nombres o si, por conveniencia, lo hacemos nosotros al invocar el #include. Así, podemos modificar facilmente nuestro main.c:

namespace POO
{
     #include "B.h"
}

int main()
{
    int var = POO::funcB(123);
    std::cout << var;
}

Puedes ver el ejemplo funcionando aquí

Este tipo de prácticas no son especialmente recomendables, pero es algo que, como puedes ver, es posible realizarlas.

Así pues, recapitulando, las funciones estarán, por normal general, bajo el espacio de nombres que haya decidido el autor de dicha librería. Los espacios de nombres no hacen magia y no aparecen y desaparecen a placer.

Ahora, hablando brevemente de la librería estándar:

  • La parte que viene heredada de C se encuentra disponible a través de dos librerías distintas, la que acaba en .h y la que empieza por c:

    #include <stdio.h>
    #include <cstdio>
    
  • La versión que empieza por c es la que se debería usar, ya que está ligeramente más adaptada a los requisitos propios de C (uso de espacio de nombres, tipado fuerte en algunos casos, ...)

  • La librería estándar propia de C++ se encuentra al completo bajo el espacio de nombres std.

Así que, como puedes ver, al usar funciones heredadas de C puedes encontrarte con ambos casos, que estén bajo std o que sean funciones libres. Todo depende de cómo las incluyas en tu código.

eferion
  • 49,291
  • 5
  • 30
  • 72
2

según la documentación cada vez que #incluyo una cabecera todo su contenido se añaden al espacio de nombres std

Supongo que te refieres a cuando incluyes una cabecera del estándar, no cuando incluyes una cabecera cualquiera, responderé asumiendo lo primero.

Me gustaría que compartieras la documentación que has leído, eso que comentas es medio cierto y puede haber cambiado entre versiones del estándar. De hecho, para comprender la situación hay que hacer un repaso de historia y señalar los temas de compatibilidad y .


Compatibilidad C y C++

Dado que C++ "se compromete" a tener una compatibilidad con C y sabiendo que ambos lenguajes son muy diferentes, las cabeceras de C estándar han sido adaptadas a C++ para poder ser usadas de manera adecuada, puedes leer sobre ello en este hilo.

Una de las diferencias entre C y C++ es que el primero carece de espacios de nombres, así que si incluyes una cabecera de C, todos sus símbolos irán a parar al espacio de nombres global a no ser que hagas "trampas":

#include <stdint.h>

namespace cabecera_de_C
{
#include <time.h>
}

En el código anterior, los símbolos de <stdint.h> carecerán de espacio de nombres mientras que los símbolos de <time.h> estarán bajo el espacio de nombres cabecera_de_C. Este ejemplo sólo es ilustrativo, porque NO debemos incluir las cabeceras estándar de C en un código C++, en su lugar debemos usar la versión adaptada a C++ que carecen de extensión y se les antepone c al nombre:

#include <cstdint>
//        ~ <--- Cabecera <stdint.h> de C adaptada a C++
#include <ctime>
//        ~ <--- Cabecera <time.h> de C adaptada a C++

Historia

En el estándar C++98 se incluyeron las cabeceras de compatibilidad con C (las mencionadas <c*>) y la idea era que sus símbolos fuesen definidos dentro del espacio de nombres std pero algunas implementaciones del estándar no lo hacen.

El estándar define cómo deben hacerse las cosas y los compiladores (GCC, CLang, MSVC, ...) implementan el estándar y en algunas cosas no se adhieren del todo al mismo, lo ideal es que la adherencia al estándar fuese total pero no siempre ese el caso.

Así que cabe la posibilidad que tu compilador no siga el estándar en la implementación de la versión que estás probando.

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82