web-dev-qa-db-fra.com

Comment une langue se développe-t-elle?

J'apprends le C++ et je viens tout juste de commencer à apprendre certaines des capacités de Qt pour coder les programmes d'interface graphique. Je me suis posé la question suivante:

Comment C++, qui auparavant n’avait pas de syntaxe capable de demander au système d’exploitation une fenêtre ou un moyen de communiquer via des réseaux (avec des API que je ne comprends pas complètement non plus, j’admets), a soudainement accès à de telles capacités à travers des bibliothèques écrites en C++ eux-mêmes? Tout cela me semble terriblement circulaire. Quelles instructions C++ pourriez-vous éventuellement trouver dans ces bibliothèques?

Je me rends compte que cette question peut sembler triviale à un développeur de logiciel expérimenté, mais je fais des recherches depuis des heures sans trouver de réponse directe. Je ne peux plus suivre le tutoriel sur Qt, car l’existence de bibliothèques m’est incompréhensible.

208
Med Larbi Sentissi

Un ordinateur est comme un oignon, il a beaucoup plusieurs couches, du noyau interne du matériel pur à la couche d’application la plus externe. Chaque couche expose des parties d'elle-même à la couche externe suivante, de sorte que la couche externe puisse utiliser certaines des fonctionnalités de la couche interne.

Dans le cas de, par exemple Windows, le système d'exploitation expose l'API WIN32 pour les applications exécutées sous Windows. La bibliothèque Qt utilise cette API pour fournir des applications utilisant Qt à sa propre API. Vous utilisez Qt, Qt utilise WIN32, WIN32 utilise des niveaux inférieurs du système d’exploitation Windows, et ainsi de suite jusqu’à ce qu’il y ait des signaux électriques dans le matériel.

195

Vous avez raison, les bibliothèques ne peuvent généralement pas rendre possible ce qui n'est pas déjà possible.

Mais les bibliothèques ne doivent pas nécessairement être écrites en C++ pour pouvoir être utilisées par un programme C++. Même s'ils sont écrits en C++, ils peuvent utiliser en interne d'autres bibliothèques non écrites en C++. Donc, le fait que C++ ne fournisse aucun moyen de le faire n'empêche pas son ajout, tant qu'il existe n pe moyen de le faire en dehors de C++.

A un niveau assez bas, certaines fonctions appelées par C++ (ou par C) seront écrites dans Assembly et l'Assembly contient les instructions requises pour faire tout ce qui n'est pas possible (ou difficile) en C++, par exemple appeler une fonction du système. À ce stade, cet appel système peut faire n'importe quoi votre ordinateur en est capable, tout simplement parce que rien ne l'arrête.

60
user743382

C et C++ ont 2 propriétés qui permettent toute cette extensibilité dont parle l’opérateur.

  1. C et C++ peuvent accéder à la mémoire
  2. C et C++ peuvent appeler le code Assembly pour obtenir des instructions qui ne sont pas en langage C ou C++.

Dans le noyau ou dans une plate-forme en mode non protégé de base, les périphériques tels que le port série ou le lecteur de disque sont mappés dans la mappe de mémoire de la même manière que RAM est. La mémoire est une série de commutateurs et en faisant basculer les commutateurs du périphérique (comme un port série ou un pilote de disque), votre périphérique sera utile.

Dans un système d'exploitation en mode protégé, lorsque l'on souhaite accéder au noyau depuis l'espace utilisateur (par exemple lors de l'écriture dans le système de fichiers ou pour dessiner un pixel à l'écran), il est nécessaire de passer un appel système. C n'a pas d'instruction pour faire des appels système, mais C peut appeler un code assembleur qui peut déclencher le bon appel système. C'est ce qui permet à son code C de communiquer avec le noyau.

Afin de faciliter la programmation d'une plate-forme particulière, les appels système sont encapsulés dans des fonctions plus complexes pouvant exécuter certaines fonctions utiles au sein de son propre programme. Il est libre d'appeler directement les appels système (à l'aide de l'assembleur), mais il est probablement plus simple d'utiliser simplement l'une des fonctions d'encapsidation fournies par la plate-forme.

Il existe un autre niveau d’API beaucoup plus utile qu’un appel système. Prenons par exemple malloc. Non seulement cela appellera le système pour obtenir de gros blocs de mémoire, mais il gérera cette mémoire en faisant toute la comptabilité tenue sur ce qui se passe.

Les API Win32 encapsulent certaines fonctionnalités graphiques avec un ensemble de widgets de plate-forme commun. Qt va un peu plus loin en encapsulant l’API Win32 (ou X Windows) de façon multiplateforme.

Fondamentalement, un compilateur C transforme le code C en code machine et, dans la mesure où l'ordinateur est conçu pour utiliser le code machine, vous devez vous attendre à ce que C soit capable de réaliser la part des lions ou de ce qu'un ordinateur peut faire. Tout ce que les bibliothèques d'encapsuleurs font, c'est faire le gros du travail pour vous afin que vous n'ayez pas à le faire.

43
doron

Les langues (comme C++ 11 ) sont spécifications, sur papier, généralement écrites en anglais. Regardez à l'intérieur du dernier brouillon C++ 11 (ou achetez le coûteux spécification finale auprès de votre fournisseur ISO).

Vous utilisez généralement un ordinateur avec un langage quelconque implémentation (Vous pouvez en principe exécuter un programme C++ sans ordinateur, par exemple en l’interprétant avec un groupe d’esclaves humains; cela serait contraire à l’éthique et inefficace)

Votre implémentation générale C++ fonctionne au-dessus d’un système d’exploitation et communique avec ce dernier (en utilisant du code spécifique à l’implémentation, souvent dans certaines bibliothèques système. Généralement, cette communication est établie via appels système . Recherchez par exemple dans syscalls (2) pour obtenir une liste des appels système disponibles sur noyau Linux .

Du point de vue de l'application, un appel système est une instruction machine élémentaire telle que SYSENTER sur x86-64 avec certaines conventions ( ABI )

Sur mon bureau Linux, les bibliothèques Qt sont au-dessus des bibliothèques X11 communiquant avec le serveur X11 Xorg à travers protocoles X Windows .

Sous Linux, utilisez ldd sur votre exécutable pour afficher la liste (longue) des dépendances sur les bibliothèques. Utilisez pmap sur votre processus en cours d'exécution pour voir ceux qui sont "chargés" à l'exécution. BTW, sous Linux, votre application n'utilise probablement que des logiciels libres, vous pouvez en étudier le code source (de Qt à Xlib, en passant par la libc, ... le noyau) pour mieux comprendre ce qui se passe.

23

Je pense que le concept qui vous manque est appels système . Chaque système d'exploitation fournit une quantité énorme de ressources et de fonctionnalités que vous pouvez utiliser pour effectuer des tâches de niveau inférieur liées au système d'exploitation. Même lorsque vous appelez une fonction de bibliothèque normale, celle-ci passe probablement un appel système en arrière-plan.

Les appels système constituent un moyen simple d’utiliser la puissance du système d’exploitation, mais ils peuvent être complexes et difficiles à utiliser. Ils sont donc souvent "encapsulés" dans des API afin que vous n’ayez pas à les traiter directement. Mais en-dessous de tout ce que vous faites qui implique des ressources liées au système d’exploitation, vous utiliserez des appels système, tels que l’impression, la mise en réseau, les sockets, etc.

Dans le cas de Windows, l'interface graphique de Microsoft Windows est réellement écrite dans le noyau. Il existe donc des appels système permettant de créer des fenêtres, de peindre des graphiques, etc. Dans d'autres systèmes d'exploitation, l'interface graphique peut ne pas faire partie du noyau, auquel cas Autant que je sache, il n'y aurait pas d'appels système pour des choses liées à l'interface graphique, et vous ne pourriez travailler qu'à un niveau encore plus bas, quels que soient les graphismes de bas niveau et les appels liés aux entrées disponibles.

19
Dave Cousineau

Bonne question. Chaque nouveau développeur C ou C++ a cela à l’esprit. Je suppose une machine x86 standard pour le reste de ce post. Si vous utilisez un compilateur Microsoft C++, ouvrez votre bloc-notes et tapez ceci (nommez le fichier Test.c)

int main(int argc, char **argv)
{
   return 0
}

Et maintenant, compilez ce fichier (à l’aide de la commande de développeur Prompt) cl Test.c /FaTest.asm

Maintenant, ouvrez Test.asm dans votre bloc-notes. Ce que vous voyez est le code traduit - C/C++ est traduit en assembleur. Avez-vous compris l'allusion?

_main   PROC
    Push    ebp
    mov ebp, esp
    xor eax, eax
    pop ebp
    ret 0
_main   ENDP

Les programmes C/C++ sont conçus pour fonctionner sur le métal. Ce qui signifie qu'ils ont accès à du matériel de niveau inférieur, ce qui facilite l'exploitation des capacités du matériel. Dis, je vais écrire un getch () sur la bibliothèque C sur une machine x86.

Selon l'assembleur, je taperais quelque chose comme ça:

_getch proc 
   xor AH, AH
   int 16h
   ;AL contains the keycode (AX is already there - so just return)
ret

Je l'exécute avec un assembleur et génère un .OBJ - nommez-le getch.obj.

J'écris alors un programme C (je n'inclue rien)

extern char getch();

void main(int, char **)
{
  getch();
}

Maintenant, nommez ce fichier - GetChTest.c. Compilez ce fichier en passant getch.obj. (Ou compilez individuellement les fichiers .obj et LINK GetChTest.Obj et getch.Obj pour produire GetChTest.exe).

Exécutez GetChTest.exe et vous constaterez qu'il attend l'entrée du clavier.

La programmation en C/C++ ne concerne pas seulement le langage. Pour être un bon programmeur C/C++, vous devez bien comprendre le type de machine qu’il utilise. Vous aurez besoin de savoir comment la gestion de la mémoire est gérée, comment les registres sont structurés, etc. Vous n’avez peut-être pas besoin de toutes ces informations pour une programmation régulière - mais elles vous aideraient énormément. Outre les connaissances de base en matériel, il est certainement utile de comprendre le fonctionnement du compilateur (c.-à-d. Sa traduction), ce qui pourrait vous permettre de modifier votre code si nécessaire. C'est un package intéressant!

Les deux langues prennent en charge le mot clé __asm, ce qui signifie que vous pouvez également mélanger le code de langue de votre assemblage. L'apprentissage de C et C++ fera de vous un programmeur globalement plus complet.

Il n'est pas nécessaire de toujours faire le lien avec Assembler. Je l'avais mentionné parce que je pensais que cela vous aiderait à mieux comprendre. Généralement, la plupart de ces appels à la bibliothèque utilisent les appels système/API fournis par le système d'exploitation (le système d'exploitation gère à son tour le travail d'interaction matérielle).

15
Krishna S Santosh

Comment C++ ... parvient-il soudainement à obtenir de telles fonctionnalités via des bibliothèques écrites en C++ eux-mêmes?

L'utilisation d'autres bibliothèques n'a rien de magique. Les bibliothèques sont de simples sacs remplis de fonctions que vous pouvez appeler.

Considérez-vous en écrivant une fonction comme celle-ci

void addExclamation(std::string &str)
{
    str.Push_back('!');
}

Maintenant, si vous incluez ce fichier, vous pouvez écrire addExclamation(myVeryOwnString);. Maintenant, vous pourriez demander: "Comment C++ a-t-il soudainement pu ajouter des points d'exclamation à une chaîne?" La réponse est simple: vous avez écrit une fonction pour le faire, puis vous l'avez appelée.

Donc, pour répondre à votre question sur la manière dont C++ peut obtenir des fonctionnalités pour dessiner des fenêtres à travers des bibliothèques écrites en C++, la réponse est la même. Quelqu'un d'autre a écrit des fonctions pour le faire, puis les a compilées et vous les a données sous la forme d'une bibliothèque.

Les autres questions portent sur le fonctionnement réel du dessin de fenêtre, mais vous sembliez confus quant au fonctionnement des bibliothèques; je voulais donc aborder la partie la plus fondamentale de votre question.

10
Philip

La clé est la possibilité pour le système d'exploitation d'exposer une API et une description détaillée de la manière dont cette API doit être utilisée.

Le système d'exploitation propose un ensemble d'API avec des conventions d'appel. La convention d'appel définit la manière dont un paramètre est donné dans l'API, comment les résultats sont renvoyés et comment exécuter l'appel lui-même.

Les systèmes d’exploitation et les compilateurs qui créent le code qui s’y trouvent jouent bien ensemble. Il est donc généralement inutile de ne pas y penser, mais de les utiliser.

8
thst

Tout d'abord, il y a un petit malentendu, je pense

Comment fonctionne le C++, qui n'avait auparavant aucune syntaxe capable de demander au système d'exploitation une fenêtre ou un moyen de communiquer via des réseaux

Il n'y a pas de syntaxe pour effectuer des opérations sur le système d'exploitation. C'est la question de sémantique .

soudainement obtenir de telles capacités à travers des bibliothèques écrites en C++ eux-mêmes

Le système d'exploitation est principalement écrit en C. Vous pouvez utiliser des bibliothèques partagées (so, dll) pour appeler le code externe. De plus, le code du système d'exploitation peut enregistrer des routines système sur appels système * ou interruptions que vous pouvez appeler à l'aide de Assembly . Les bibliothèques partagées ne font souvent que des appels système à votre place, vous évitant ainsi d’utiliser Assembly en ligne.

Voici le tutoriel de Nice sur ce sujet: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
C'est pour Linux, mais les principes sont les mêmes.

Comment le système d'exploitation effectue-t-il les opérations sur les cartes graphiques, les cartes réseau, etc.? C'est un thème très large, mais vous devez principalement accéder aux interruptions, aux ports ou écrire des données dans une région mémoire spéciale. Puisque ces opérations sont protégées, vous devez quand même les appeler via le système d'exploitation.

7
Danubian Sailor

Pour tenter de donner un point de vue légèrement différent d’autres réponses, je vais répondre comme ceci.

(Avertissement: je simplifie un peu les choses, la situation que je vous présente est purement hypothétique et est écrite comme un moyen de démontrer des concepts plutôt que d'être fidèle à 100%).

Imaginez que vous venez d’écrire un système d’exploitation simple avec des fonctionnalités de base de threading, de fenêtrage et de gestion de la mémoire. Vous souhaitez implémenter une bibliothèque C++ pour permettre aux utilisateurs de programmer en C++ et de faire des choses comme créer des fenêtres, dessiner sur des fenêtres, etc. La question est de savoir comment faire.

Premièrement, puisque C++ est compilé en code machine, vous devez définir un moyen d’utiliser le code machine pour l’interface avec C++. C’est là que les fonctions entrent en jeu, que les fonctions acceptent les arguments et donnent des valeurs de retour; elles fournissent donc un moyen standard de transférer des données entre différentes sections de code. Ils le font en établissant quelque chose connu sous le nom de convention d'appel .

Une convention d'appel ( indique où et comment les arguments doivent être placés en mémoire afin qu'une fonction puisse les trouver lorsqu'ils sont exécutés. Lorsqu'une fonction est appelée, la fonction appelante place les arguments en mémoire, puis demande à la CPU de passer à l'autre fonction, où elle fait ce qu'elle fait avant de revenir à l'endroit où elle a été appelée. Cela signifie que le code appelé peut être absolument n'importe quoi et cela ne changera pas le nom de la fonction. Dans ce cas, toutefois, le code derrière la fonction serait pertinent pour le système d'exploitation et fonctionnerait à l'état interne du système d'exploitation.

Ainsi, plusieurs mois plus tard, toutes vos fonctions de système d'exploitation sont définies. Votre utilisateur peut appeler des fonctions pour créer des fenêtres et y dessiner, ils peuvent créer des threads et toutes sortes de choses merveilleuses. Voici le problème cependant, les fonctions de votre système d'exploitation vont être différentes des fonctions de Linux ou de Windows. Vous décidez donc de donner à l'utilisateur une interface standard lui permettant d'écrire du code portable. Voici où QT entre en jeu.

Comme vous le savez presque certainement, QT dispose d'un grand nombre de classes et de fonctions utiles pour effectuer les tâches que font les systèmes d'exploitation, mais d'une manière qui semble indépendante du système d'exploitation sous-jacent. QT fournit des classes et des fonctions uniformes pour l'utilisateur, mais le code derrière ces fonctions est différent pour chaque système d'exploitation. Par exemple, QApplication :: closeAllWindows () de QT appelle en fait la fonction de fermeture de fenêtre spécialisée de chaque système d'exploitation en fonction de la version utilisée. Sous Windows, il est fort probable qu'il appelle CloseWindow (hwnd), tandis que sous un système d'exploitation utilisant le système X Window, il pourrait potentiellement appeler XDestroyWindow (display, window).

Comme il est évident, un système d'exploitation comporte de nombreuses couches, qui doivent toutes interagir par le biais d'interfaces de nombreuses variétés. Il y a de nombreux aspects que je n'ai même pas abordés, mais les expliquer tous prendrait beaucoup de temps. Si vous êtes intéressé par le fonctionnement interne des systèmes d'exploitation, je vous recommande de consulter le OS dev wiki .

Gardez à l’esprit cependant que de nombreux systèmes d’exploitation choisissent d’exposer les interfaces en C/C++ parce qu’ils compilent en code machine, ils permettent aux instructions d’Assembly d’être mélangées à leur propre code et offrent une grande liberté au programmeur.

Encore une fois, il se passe beaucoup de choses ici. Je voudrais ensuite expliquer comment des bibliothèques telles que les fichiers .so et .dll ne doivent pas nécessairement être écrites en C/C++ et peuvent être écrites en Assembly ou dans d’autres langages, mais j’estime que, si j’ajoute quelque chose, je pourrais aussi écrivez un article entier, et autant que j'aimerais faire cela, je n'ai pas de site pour l'héberger.

7
Pharap

Une syntaxe spéciale n'est pas nécessaire pour créer des fenêtres. Tout ce qui est requis est que le système d'exploitation fournisse une API pour créer des fenêtres. Une telle API consiste en de simples appels de fonctions pour lesquels C++ fournit la syntaxe.

De plus, C et C++ sont ce qu'on appelle des langages de programmation système et peuvent accéder à des pointeurs arbitraires (qui peuvent être mappés sur un périphérique par le matériel). De plus, il est également assez simple d'appeler des fonctions définies dans Assembly, ce qui permet à l'ensemble des opérations fournies par le processeur. Par conséquent, il est possible d'écrire un système d'exploitation lui-même en utilisant C ou C++ et une petite quantité d'Assembly.

Il convient également de mentionner que Qt est un mauvais exemple, car il utilise un méta compilateur à la syntaxe extend C++ '. Cela n’est cependant pas lié à sa capacité à appeler les API fournies par le système d’exploitation pour dessiner ou créer des fenêtres.

7
Joe

Lorsque vous essayez de dessiner quelque chose à l'écran, votre code appelle un autre morceau de code qui appelle un autre code (etc.) jusqu'à ce qu'il y ait un "appel système", qui est une instruction spéciale que le CPU peut exécuter. Ces instructions peuvent être écrites dans Assembly ou C++ si le compilateur prend en charge leurs "fonctions intrinsèques" (fonctions que le compilateur gère "spécialement" en les convertissant en un code spécial que le CPU peut comprendre). Leur travail consiste à dire au système d’exploitation de faire quelque chose.

Lorsqu'un appel système se produit, une fonction appelée, appelle une autre fonction (etc.) jusqu'à ce que le pilote d'affichage reçoive le message de dessiner quelque chose sur l'écran. À ce stade, le pilote d’affichage examine une région particulière de mémoire physique qui est en réalité pas mémoire, mais plutôt une plage d’adresses sur laquelle il est possible d’écrire comme si c'était de la mémoire. Cependant, si vous écrivez dans cette plage d'adresses, les graphismes matériel intercepteront l'écriture en mémoire et dessineront quelque chose à l'écran.
L'écriture dans cette zone de mémoire est quelque chose qui pourrait être codé en C++, car du côté logiciel, il s'agit simplement d'un accès à la mémoire normal. C'est juste que le matériel le gère différemment.
Voilà donc une explication très élémentaire de la façon dont cela peut fonctionner.

6
Mehrdad

Votre programme C++ utilise la bibliothèque Qt (également codée en C++). La bibliothèque Qt utilisera la fonction Windows CreateWindowEx (codée en C dans kernel32.dll). Ou sous Linux, il peut utiliser Xlib (également codé en C), mais il pourrait aussi bien envoyer les octets bruts qui, dans le protocole X, signifient " Veuillez créer un fenêtre pour moi ".

Relatif à votre question catch-22 est la note historique selon laquelle "le premier compilateur C++ a été écrit en C++", bien qu'en réalité c'était un compilateur C avec quelques notions C++, suffisamment pour pouvoir compiler la première version, qui pourrait ensuite se compiler.

De même, le compilateur GCC utilise les extensions GCC: il est d'abord compilé en une version puis utilisé pour se recompiler. (instructions de construction de GCC)

4
Ángel

Comment je vois la question c'est en fait une question de compilateur.

Regardez ceci de cette façon, vous écrivez un morceau de code dans Assembly (vous pouvez le faire dans n’importe quel langage) qui traduit votre langage nouvellement écrit que vous voulez appeler Z ++ dans Assembly, pour plus de simplicité, appelons-le un compilateur (c’est un compilateur) .

Maintenant, vous donnez à ce compilateur des fonctions de base, de sorte que vous puissiez écrire int, chaîne, tableaux, etc. En fait, vous lui donnez suffisamment de capacités pour pouvoir écrire le compilateur lui-même en Z ++. et maintenant vous avez un compilateur pour Z ++ écrit en Z ++, très joli droit.

Ce qui est encore plus cool, c'est que vous pouvez maintenant ajouter des capacités à ce compilateur en utilisant les capacités qu'il possède déjà, étendant ainsi le langage Z ++ avec de nouvelles fonctionnalités en utilisant les fonctionnalités précédentes.

Par exemple, si vous écrivez suffisamment de code pour dessiner un pixel dans n’importe quelle couleur, vous pouvez le développer à l’aide du Z ++ pour dessiner ce que vous voulez.

2
Thomas Andreè Lian

Le matériel est ce qui permet que cela se produise. Vous pouvez considérer la mémoire graphique comme un grand tableau (composé de chaque pixel de l’écran). Pour dessiner à l'écran, vous pouvez écrire dans cette mémoire en utilisant C++ ou tout autre langage permettant un accès direct à cette mémoire. Cette mémoire est simplement accessible ou située sur la carte graphique.

Sur les systèmes modernes, l'accès direct à la mémoire graphique nécessiterait l'écriture d'un pilote en raison de diverses restrictions, de sorte que vous utilisez des moyens indirects. Les bibliothèques qui créent une fenêtre (en réalité une image comme toute autre image), puis écrivent cette image dans la mémoire graphique, que le processeur graphique affiche ensuite à l'écran. Rien ne doit être ajouté à la langue sauf la possibilité d'écrire dans des emplacements de mémoire spécifiques, pour lesquels les pointeurs sont utilisés.

0
john