boost c++
¿Cómo se expande un lenguaje? (15)
Estoy aprendiendo C ++ y acabo de comenzar a aprender sobre algunas de Qt capacidades de Qt para codificar programas GUI. Me hice la siguiente pregunta:
¿Cómo puede C ++, que anteriormente no tenía una sintaxis capaz de pedirle al sistema operativo una ventana o una forma de comunicarse a través de redes (con API que tampoco entiendo completamente, lo admito) de repente obtiene esas capacidades a través de bibliotecas escritas en C ++? Todo me parece terriblemente circular. ¿Qué instrucciones de C ++ podrías encontrar en esas bibliotecas?
Me doy cuenta de que esta pregunta puede parecer trivial para un desarrollador de software experimentado, pero he estado investigando durante horas sin encontrar ninguna respuesta directa. Llegué al punto en el que no puedo seguir el tutorial sobre Qt porque la existencia de bibliotecas es incomprensible para mí.
¿Cómo obtiene C ++ ... de repente estas capacidades a través de las bibliotecas escritas en C ++?
No hay nada mágico en usar otras bibliotecas. Las bibliotecas son simples bolsas de funciones que puedes llamar.
Considera escribir una función como esta
void addExclamation(std::string &str)
{
str.push_back(''!'');
}
Ahora, si incluye ese archivo, puede escribir addExclamation(myVeryOwnString);
. Ahora puede preguntar, "¿cómo obtuvo C ++ repentinamente la capacidad de agregar puntos de exclamación a una cadena?" La respuesta es fácil: escribiste una función para hacer eso y luego la llamaste.
Entonces, para responder a su pregunta sobre cómo C ++ puede obtener capacidades para dibujar ventanas a través de bibliotecas escritas en C ++, la respuesta es la misma. Alguien más escribió funciones para hacer eso, y luego las compiló y se las dio en forma de biblioteca.
Las otras preguntas responden cómo funciona realmente el dibujo de la ventana, pero parecía confundido acerca de cómo funcionan las bibliotecas, así que quería abordar la parte más fundamental de su pregunta.
Buena pregunta. Cada nuevo desarrollador de C o C ++ tiene esto en mente. Estoy asumiendo una máquina x86 estándar para el resto de este post. Si está utilizando el compilador de Microsoft C ++, abra su bloc de notas y escriba esto (nombre del archivo Test.c)
int main(int argc, char **argv)
{
return 0
}
Y ahora compile este archivo (usando el indicador de comando del desarrollador) cl Test.c /FaTest.asm
Ahora abre Test.asm en tu bloc de notas. Lo que ve es el código traducido - C / C ++ se traduce al ensamblador. ¿Entiendes la pista?
_main PROC
push ebp
mov ebp, esp
xor eax, eax
pop ebp
ret 0
_main ENDP
Los programas C / C ++ están diseñados para ejecutarse en el metal. Lo que significa que tienen acceso a hardware de nivel inferior, lo que facilita la explotación de las capacidades del hardware. Digamos que voy a escribir un getch () de la biblioteca C en una máquina x86.
Dependiendo del ensamblador yo escribiría algo de esta manera:
_getch proc
xor AH, AH
int 16h
;AL contains the keycode (AX is already there - so just return)
ret
Lo ejecuto con un ensamblador y genero un .OBJ: llámalo getch.obj.
Luego escribo un programa en C (No #incluyo nada)
extern char getch();
void main(int, char **)
{
getch();
}
Ahora nombre este archivo - GetChTest.c. Compila este archivo pasando getch.obj a lo largo. (O compile individualmente en .obj y LINK GetChTest.Obj y getch.Obj juntos para producir GetChTest.exe).
Ejecute GetChTest.exe y encontrará que espera la entrada del teclado.
La programación de C / C ++ no es solo sobre el lenguaje. Para ser un buen programador de C / C ++, debe tener una buena comprensión del tipo de máquina que ejecuta. Necesitará saber cómo se maneja la administración de la memoria, cómo se estructuran los registros, etc. Es posible que no necesite toda esta información para la programación regular, pero que le ayudarán enormemente. Además del conocimiento básico de hardware, sin duda es útil si comprende cómo funciona el compilador (es decir, cómo se traduce), lo que podría permitirle modificar su código según sea necesario. ¡Es un paquete interesante!
Ambos idiomas admiten la palabra clave __asm, lo que significa que también puede combinar su código de lenguaje ensamblador. Aprender C y C ++ lo convertirá en un mejor programador en general.
No es necesario enlazar siempre con el ensamblador. Lo mencioné porque pensé que eso te ayudaría a entender mejor. En su mayoría, la mayoría de las llamadas a bibliotecas hacen uso de las llamadas de sistema / API proporcionadas por el sistema operativo (el sistema operativo a su vez hace las cosas de interacción de hardware).
C y C ++ tienen 2 propiedades que permiten toda la extensibilidad de la que habla el OP.
- C y C ++ pueden acceder a la memoria.
- C y C ++ pueden llamar al código de ensamblaje para obtener instrucciones que no estén en el lenguaje C o C ++.
En el kernel o en una plataforma básica de modo no protegido, los periféricos como el puerto serie o la unidad de disco se asignan al mapa de memoria de la misma manera que la memoria RAM. La memoria es una serie de interruptores y al presionar los interruptores del periférico, el puerto serie o la unidad de disco pueden hacer cosas útiles.
En un sistema operativo en modo protegido, cuando uno quiere acceder al kernel desde el espacio de usuario (por ejemplo, al escribir en el sistema de archivos o dibujar un píxel en la pantalla), se necesita hacer una llamada al sistema. C no tiene instrucciones para hacer una llamada al sistema, pero C puede llamar al código del ensamblador que puede activar la llamada correcta al sistema que permite al código C de uno hablar con el kernel.
Para facilitar la programación de una plataforma en particular, las llamadas al sistema están envueltas en funciones más complejas, lo que puede realizar alguna función útil dentro del propio programa. Uno es gratis llamar directamente a las llamadas del sistema (usando ensamblador), pero probablemente sea más fácil hacer uso de una de las funciones de envoltorio que proporciona la plataforma.
Hay otro nivel de API que es mucho más útil que una llamada al sistema. Tomemos por ejemplo malloc. Esto no solo llamará al sistema para obtener grandes bloques de memoria, sino que administrará esta memoria haciendo todo el mantenimiento de lo que se lleva a cabo.
Las API de Win32 envuelven algunas funcionalidades gráficas con un conjunto de widgets de plataforma común. Qt lleva esto un poco más lejos envolviendo la API de Win32 (o X Windows) de forma multiplataforma.
Fundamentalmente, aunque un compilador de C convierte el código de C en un código de máquina y como la computadora está diseñada para usar el código de la máquina, debe esperar que C pueda lograr la parte de los leones o lo que puede hacer una computadora. Todo lo que hacen las bibliotecas de envoltorios es hacer el trabajo pesado por usted para que no tenga que hacerlo.
Cómo veo la pregunta, esta es en realidad una pregunta del compilador.
Míralo de esta manera, escribes un fragmento de código en Assembly (puedes hacerlo en cualquier idioma) que traduce tu lenguaje recién escrito al que quieres llamar Z ++ en Assembly, por simplicidad, llamémoslo compilador (es un compilador). .
Ahora le da a este compilador algunas funciones básicas, de modo que puede escribir int, string, arrays, etc. En realidad, le da suficientes habilidades para que pueda escribir el compilador en Z ++. y ahora tienes un compilador para Z ++ escrito en Z ++, bastante bien.
Lo que es aún más genial es que ahora puedes agregar habilidades a ese compilador usando las habilidades que ya tiene, expandiendo así el lenguaje Z ++ con nuevas características usando las funciones anteriores
Un ejemplo, si escribe suficiente código para dibujar un píxel en cualquier color, entonces puede expandirlo usando Z ++ para dibujar lo que desee.
Creo que el concepto que te faltan son las llamadas al sistema . Cada sistema operativo proporciona una enorme cantidad de recursos y funcionalidades que puede aprovechar para hacer cosas relacionadas con el sistema operativo de bajo nivel. Incluso cuando llama a una función de biblioteca normal, es probable que esté haciendo una llamada al sistema detrás de la escena.
Las llamadas al sistema son una forma de bajo nivel de hacer uso del poder del sistema operativo, pero pueden ser complejas y complicadas de usar, por lo que a menudo están "envueltas" en las API para que no tenga que tratarlas directamente. Pero debajo, casi cualquier cosa que haga que involucre recursos relacionados con O / S usará llamadas al sistema, incluyendo impresión, redes y sockets, etc.
En el caso de Windows, Microsoft Windows tiene su GUI realmente escrita en el kernel, por lo que hay llamadas al sistema para hacer ventanas, pintar gráficos, etc. En otros sistemas operativos, la GUI puede no ser parte del kernel, en cuyo caso Por lo que sé, no habría ninguna llamada del sistema para cosas relacionadas con la GUI, y solo podría trabajar en un nivel aún más bajo con los gráficos de bajo nivel y las llamadas relacionadas con la entrada disponibles.
Cuando intenta dibujar algo en la pantalla, su código llama a otra pieza de código que llama a otro código (etc.) hasta que finalmente hay una "llamada al sistema", que es una instrucción especial que la CPU puede ejecutar. Estas instrucciones se pueden escribir en ensamblaje o se pueden escribir en C ++ si el compilador admite sus "intrínsecos" (que son funciones que el compilador maneja "especialmente" al convertirlas en un código especial que la CPU puede entender). Su trabajo es decirle al sistema operativo que haga algo.
Cuando ocurre una llamada al sistema, se llama a una función que llama a otra función (etc.) hasta que finalmente se le indica al controlador de pantalla que dibuje algo en la pantalla. En ese punto, el controlador de pantalla mira una región particular en la memoria física que en realidad no es memoria, sino un rango de direcciones en el que se puede escribir como si fuera memoria. Sin embargo, al escribir en ese rango de direcciones, el hardware de gráficos intercepta la escritura de la memoria y dibuja algo en la pantalla.
Escribir en esta región de la memoria es algo que podría codificarse en C ++, ya que en el lado del software es solo un acceso regular a la memoria. Es solo que el hardware lo maneja de manera diferente.
Así que esa es una explicación realmente básica de cómo puede funcionar.
El hardware es lo que permite que esto suceda. Puede pensar en la memoria de gráficos como una gran matriz (que consta de cada píxel en la pantalla). Para dibujar en la pantalla, puede escribir en esta memoria utilizando C ++ o cualquier idioma que permita el acceso directo a esa memoria. Esa memoria solo es accesible por o ubicada en la tarjeta gráfica.
En los sistemas modernos, acceder directamente a la memoria de gráficos requeriría escribir un controlador debido a varias restricciones, por lo que se usan medios indirectos. Las bibliotecas que crean una ventana (en realidad solo una imagen como cualquier otra imagen) y luego escriben esa imagen en la memoria de gráficos que la GPU luego muestra en la pantalla. No se debe agregar nada al idioma, excepto la capacidad de escribir en ubicaciones de memoria específicas, que es para lo que sirven los punteros.
En un intento por proporcionar una visión ligeramente diferente a otras respuestas, responderé así.
(Descargo de responsabilidad: estoy simplificando las cosas ligeramente, la situación que doy es puramente hipotética y está escrita como un medio para demostrar conceptos en lugar de ser 100% fiel a la vida).
Piense en las cosas desde la otra perspectiva, imagine que acaba de escribir un sistema operativo sencillo con capacidades básicas de gestión de memoria, creación de ventanas y ventanas. Desea implementar una biblioteca de C ++ para permitir a los usuarios programar en C ++ y hacer cosas como crear ventanas, dibujar en ventanas, etc. La pregunta es cómo hacer esto.
En primer lugar, dado que C ++ compila a código de máquina, debe definir una manera de usar el código de máquina para interactuar con C ++. Aquí es donde entran las funciones, las funciones aceptan argumentos y dan valores de retorno, por lo que proporcionan una forma estándar de transferir datos entre diferentes secciones de código. Lo hacen estableciendo algo conocido como una convención de vocación .
Una convención de llamada establece dónde y cómo se deben colocar los argumentos en la memoria para que una función pueda encontrarlos cuando se ejecute. Cuando se llama a una función, la función que llama coloca los argumentos en la memoria y luego le pide a la CPU que salte a la otra función, donde hace lo que hace antes de saltar al lugar desde el que fue llamada. Esto significa que el código al que se llama puede ser absolutamente cualquier cosa y no cambiará la forma en que se llama la función. Sin embargo, en este caso, el código detrás de la función sería relevante para el sistema operativo y operaría en el estado interno del sistema operativo.
Entonces, muchos meses después y ya tienes todas las funciones de tu sistema operativo resueltas. Tu usuario puede llamar a las funciones para crear ventanas y dibujarlas, pueden crear hilos y todo tipo de cosas maravillosas. Sin embargo, aquí está el problema, las funciones de su sistema operativo serán diferentes a las funciones de Linux o de Windows. Entonces, decide que necesita darle al usuario una interfaz estándar para que puedan escribir código portátil. Aquí es donde entra QT.
Como casi con seguridad sabrá, QT tiene muchas clases y funciones útiles para hacer el tipo de cosas que hacen los sistemas operativos, pero de una manera que parece independiente del sistema operativo subyacente. La forma en que funciona es que QT proporciona clases y funciones que son uniformes en la forma en que aparecen para el usuario, pero el código detrás de las funciones es diferente para cada sistema operativo. Por ejemplo, QA QApplication :: closeAllWindows () en realidad estaría llamando a la función de cierre de ventana especializada de cada sistema operativo, dependiendo de la versión utilizada. En Windows, lo más probable es que llame a CloseWindow (hwnd), mientras que en un sistema operativo que usa el sistema X Window, podría llamar a XDestroyWindow (pantalla, ventana).
Como es evidente, un sistema operativo tiene muchas capas, todas las cuales tienen que interactuar a través de interfaces de muchas variedades. Hay muchos aspectos que ni siquiera he tocado, pero explicarlos todos llevaría mucho tiempo. Si estás interesado en el funcionamiento interno de los sistemas operativos, te recomiendo que consultes el wiki de desarrollo del sistema operativo .
Sin embargo, tenga en cuenta que la razón por la que muchos sistemas operativos eligen exponer las interfaces a C / C ++ es que se compilan a código de máquina, permiten que las instrucciones de ensamblaje se mezclen con su propio código y brindan una gran libertad al programador.
Una vez más, hay mucho que hacer aquí. Me gustaría continuar explicando cómo las bibliotecas como .so y .dll no tienen que estar escritas en C / C ++ y pueden estar escritas en ensamblador u otros idiomas, pero creo que si agrego más, también podría escriba un artículo completo, y por mucho que me gustaría hacer eso, no tengo un sitio para alojarlo.
La clave es la posibilidad del sistema operativo de exponer una API y una descripción detallada de cómo se utilizará esta API.
El sistema operativo ofrece un conjunto de API con convenciones de llamada. La convención de llamada está definiendo la forma en que se asigna un parámetro en la API y cómo se devuelven los resultados y cómo ejecutar la llamada real.
Los sistemas operativos y los compiladores que crean el código para ellos juegan muy bien juntos, por lo que normalmente no tienes que pensar en eso, solo úsalo.
Los idiomas (como C++11 ) son especificaciones , en papel, generalmente escritas en inglés. Mire dentro del último borrador de C ++ 11 (o compre la costosa especificación final de su proveedor de ISO).
Por lo general, usa una computadora con alguna implementación de lenguaje (en principio, podría ejecutar un programa C ++ sin ninguna computadora, por ejemplo, usando un grupo de esclavos humanos interpretándolo; eso sería poco ético e ineficiente).
Su implementación de C ++ en general funciona por encima de algunos sistemas operativos y se comunica con ellos (utilizando algún código específico de implementación , a menudo en alguna biblioteca del sistema). Generalmente esa comunicación se realiza a través de llamadas al sistema . Busque, por ejemplo, en syscalls(2) una lista de llamadas al sistema disponibles en el kernel de Linux .
Desde el punto de vista de la aplicación, un syscall es una instrucción de máquina elemental como SYSENTER
en x86-64 con algunas convenciones ( ABI )
En mi escritorio Linux, las bibliotecas Qt están por encima de las bibliotecas cliente X11 que se comunican con el servidor X11 Xorg través de los protocolos X Windows .
En Linux, use ldd
en su ejecutable para ver la lista (larga) de dependencias en las bibliotecas. Utilice pmap
en su proceso de ejecución para ver cuáles se "cargan" en tiempo de ejecución. Por cierto, en Linux, su aplicación probablemente esté usando solo software libre, podría estudiar su código fuente (de Qt, a Xlib, libc, ... el kernel) para comprender más de lo que está sucediendo.
No hay necesidad de una sintaxis especial para crear ventanas. Todo lo que se requiere es que el sistema operativo proporcione una API para crear ventanas. Dicha API consta de llamadas a funciones simples para las que C ++ proporciona sintaxis.
Además, C y C ++ se denominan lenguajes de programación de sistemas y pueden acceder a punteros arbitrarios (que pueden estar asignados a algún dispositivo por el hardware). Además, también es bastante simple llamar a las funciones definidas en el ensamblaje, lo que permite la gama completa de operaciones que proporciona el procesador. Por lo tanto, es posible escribir un sistema operativo en sí usando C o C ++ y una pequeña cantidad de ensamblaje.
También se debe mencionar que Qt es un mal ejemplo, ya que utiliza el llamado compilador meta para extender la sintaxis de C ++. Sin embargo, esto no está relacionado con su capacidad de llamar a las API proporcionadas por el sistema operativo para dibujar o crear ventanas.
Primero, hay un poco de malentendido, creo
¿Cómo funciona C ++, que anteriormente no tenía una sintaxis capaz de pedirle al sistema operativo una ventana o una forma de comunicarse a través de las redes?
No hay sintaxis para hacer las operaciones del sistema operativo. Es la cuestión de la semántica .
De repente, obtenga dichas capacidades a través de bibliotecas escritas en C ++.
Bueno, el sistema operativo está escrito principalmente en C. Puede usar bibliotecas compartidas (por lo tanto, dll) para llamar al código externo. Además, el código del sistema operativo puede registrar rutinas del sistema en syscalls * o interrupciones a las que puede llamar usando ensamblador . Las bibliotecas compartidas a menudo solo hacen que el sistema lo requiera, por lo que se ahorra el uso del ensamblaje en línea.
Aquí está el buen tutorial sobre eso: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
Es para Linux, pero los principios son los mismos.
¿Cómo funciona el sistema operativo en tarjetas gráficas, tarjetas de red, etc.? Es un tema muy amplio, pero principalmente necesita acceder a interrupciones, puertos o escribir algunos datos en la región de memoria especial. Dado que las operaciones están protegidas, debe llamarlas a través del sistema operativo de todos modos.
Su programa C ++ está utilizando la biblioteca Qt (también codificada en C ++). La biblioteca Qt utilizará la función Windows CreateWindowEx (que fue codificada en C dentro de kernel32.dll). O bajo Linux puede estar usando Xlib (también codificado en C), pero también podría estar enviando los bytes en bruto que en el protocolo X significa " Por favor, cree una ventana para mí ".
Relacionada con su pregunta catch-22 está la nota histórica de que "el primer compilador de C ++ se escribió en C ++", aunque en realidad era un compilador de C con algunas nociones de C ++, lo suficiente como para compilar la primera versión, que luego podría compilarse .
De manera similar, el compilador GCC usa extensiones GCC: primero se compila a una versión y luego se usa para recompilarse a sí mismo. (Instrucciones de construcción GCC)
Tienes razón en que, en general, las bibliotecas no pueden hacer nada posible que ya no sea posible.
Pero las bibliotecas no tienen que estar escritas en C ++ para poder ser utilizadas por un programa de C ++. Incluso si están escritas en C ++, pueden usar internamente otras bibliotecas no escritas en C ++. Por lo tanto, el hecho de que C ++ no proporcione ninguna forma de hacerlo no impide que se agregue, siempre que haya alguna forma de hacerlo fuera de C ++.
En un nivel bastante bajo, algunas funciones llamadas por C ++ (o por C) se escribirán en ensamblaje, y el ensamblaje contiene las instrucciones necesarias para hacer lo que no es posible (o no es fácil) en C ++, por ejemplo, para llamar a Una función del sistema. En ese punto, esa llamada al sistema puede hacer cualquier cosa que su computadora pueda hacer, simplemente porque no hay nada que lo detenga.
Una computadora es como una cebolla, tiene muchas capas, desde el núcleo interno del hardware puro hasta la capa de aplicación más externa. Cada capa expone partes de sí misma a la siguiente capa externa, de modo que la capa externa puede usar parte de la funcionalidad de las capas internas.
En el caso de, por ejemplo, Windows, el sistema operativo expone la llamada API WIN32 para aplicaciones que se ejecutan en Windows. La biblioteca Qt usa esa API para proporcionar aplicaciones usando Qt a su propia API. Usted usa Qt, Qt usa WIN32, WIN32 usa niveles más bajos del sistema operativo Windows, y así sucesivamente hasta que haya señales eléctricas en el hardware.