IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C Discussion :

representation d'une variable et son pointeur


Sujet :

C

  1. #1
    Membre à l'essai
    Homme Profil pro
    autre
    Inscrit en
    Octobre 2018
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : autre

    Informations forums :
    Inscription : Octobre 2018
    Messages : 30
    Points : 21
    Points
    21
    Par défaut representation d'une variable et son pointeur
    Bonjour,

    mon problème vient de la conceptualisation de la représentation des données en mémoire.
    En partant du principe que la mémoire est un ruban de longueur pseudo-infini, je vois deux manières de représenter le code suivant.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int n;
    n = 42;
    int *p;
    p = &n;
    Nom : var + poin 1.1.png
Affichages : 800
Taille : 3,9 Ko
    creation d'une zone memoire (n) pour contenir un int
    Nom : var + poin 2.1.png
Affichages : 799
Taille : 3,8 Ko
    initialisation de n avec la valeur 42
    Nom : var + poin 3.1.png
Affichages : 815
Taille : 8,1 Ko
    creation d'un pointeur (p) sur int
    Nom : var + poin 4.1.png
Affichages : 796
Taille : 8,8 Ko
    initialisation de p avec l'adresse de n



    Ou bien de se dire que la mémoire (le ruban) est divisée en une unité atomique (l'octet) et crée un tableau référençant toutes ces cases. C'est-à-dire un tableau contenant les pointeurs de chaque case mémoire. Et à chaque création de zone mémoire, l'adresse de début de cette zone mémoire pointe vers celle-ci.
    Nom : var + poin with tab 1.1.png
Affichages : 801
Taille : 12,0 KoNom : var + poin with tab 2.1.png
Affichages : 928
Taille : 12,4 KoNom : var + poin with tab 3.1.png
Affichages : 904
Taille : 14,7 Ko
    création d'une zone mémoire (n) pour contenir un int puis initialisation de n avec la valeur 42. Création d'un pointeur (p) sur int
    Nom : var + poin with tab 4.png
Affichages : 788
Taille : 15,4 Ko
    initialisation de p avec l'adresse de n

    cette différence de vue, implique la création d'un tableau et éventuellement de drapeaux dans les cases de ce tableau pour indiquer que les cases sont occupées. Il y a là une dissociation entre les données et leur référencement un peu comme dans le cas des disques durs avec les clusters et la table d'allocation du système de fichiers.

    Si quelqu'un pouvait me faire savoir qu'elle est la bonne manière de voir, d'avance merci à lui/elle.

    PS:Je me rend compte que cette question a largement dérivé vers une question de système informatique/système d'exploitation.

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Admettons que la mémoire soit "1 gros tableau" (spoiler : c'est faux), ta dernière image "var-p-poin-with-tab-4.png" est presque correcte en remplaçant le nom de la variable "*n" par "foo".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    | x0 40  |
    | x0 41  | -> n: 63
    | x0 42  |
    | x0 43  |
    | x0 44  |
    | x0 45  | -> foo: x0 41 
    | x0 46  |
    | x0 47  |
    C'est le pointeur qui tu déférences pour aller chercher la valeur vers laquelle il pointe.
    • foo : pointeur, contient 1 adresse
    • *foo : valeur contenue à l'adresse

  3. #3
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par primusG Voir le message
    Si quelqu'un pouvait me faire savoir qu'elle est la bonne manière de voir, d'avance merci à lui/elle.
    Je vois pas trop les différences entre les deux illustrations (remarquablement bien faites, soit dit en passant).
    Dans la première, tu représentes une case de la mémoire, qui contient la valeur 42. Puis une seconde case qui contient l'adresse de la première.
    Et dans la seconde, tu as la même représentation mais tout ceci au milieu de la RAM. Mais c'est la même chose (sauf que ta seconde montre une valeur "63" alors que tu parles de "42" mais ça doit-être un oubli de MAJ).

    Toutefois un minuscule détail qui, à la fois peut faire une différence mais qui en réalité, n'en fait pas. Ok, désolé de faire le normand, je m'explique. Toutefois je vais reprendre ton exemple mais en utilisant un float parce que j'aurai à un moment besoin de parler d'un int mais je ne veux pas mélanger le "int" de mon explication avec le "int" de ton exemple. Donc si tu admets que l'explication qui va suivre via le float peut ensuite s'appliquer à tous les types du C tu auras tout pigé.
    Donc prenons un float. Un float c'est quatre octets. Donc quand tu écris float n=3.1415927, tu as la variable "n" qui s'étale sur 4 octets mémoire (on va dire 0x40, 0x41, 0x42 et 0x43) qui tous servent à stocker la valeur 3.1415927 (chacun prend une part).
    Puis quand tu écris float* p=&n tu as la variable "p" qui récupère l'adresse de "n" mais quelle adresse? Ben celle qui commence la zone, donc 0x40.
    C'est ensuite quand tu demandes à récupérer *p, le C connaissant le type de "étoile p" (un float), saura qu'il faut prendre 4 octets à partir de l'adresse 0x40. Et comment le sait-il? Parce que l'auras écrit. Car en écrivant float* p (que tu as aussi le droit d'écrire float *p ou même float * p, c'est au goût de chacun) tu lui donnes deux infos
    • p est un pointeur (une variable prévue pour recevoir une adresse)
    • étoile p est un float, donc si on te demande "étoile p" il te faudra lire un float donc 4 octets

    Hé oui, si tu réfléchis bien, à la base 0x40 c'est un simple nombre (64 en décimal) qu'on aurait pu alors stocker dans un "int" (et c'est là que mon "int" entre dans l'explication mais qui ne doit pas être confondu avec celui de ton exemple). Mais si on écrit (ou si on avait le droit d'écrire) int p=&n, le C n'aurait absolument aucun moyen de savoir qu'à l'adresse 0x40 (qui est, comme tu le dis, noyée au milieu d'un ruban infini) il faut prendre 1 (un char), 2 (un short), 4 (un long/un float) ou 8 (un double) octets.

    Mais pourquoi cela ne change en réalité rien du tout? Parce que pour toi, le C ne travaille pas en octets mais en unités de ce que tu déclares. Imagine un tableau de float (float t[]={0.1, 0.2, 0.3}), ce tableau s'étale sur 3 floats donc 3*4 octets (0x40, 0x41, 0x42 et 0x43 pour t[0] ; 0x44, 0x45, 0x46 et 0x47 pour t[1] ; 0x48, 0x49, 0x50 et 0x51 pour t[2]). Ensuite si tu déclares un pointeur p sur t (float* p=t), tu as "p" qui vaut 0x40. Mais si tu incrémentes "p" (p++), p ne passera pas à 0x41 mais 0x44, parce que le C, travaillant ici en float, considère que l'instruction p++ signifie "place-toi sur le float suivant" (ce qui est généralement le but du programmeur, qui n'en a rien à cirer de l'adresse 0x41, lui il veut simplement obtenir la case suivante du taleau). Et c'est ainsi qu'on peut utiliser un pointeur pour traiter un tableau: en le mettant sur la première case et en l'incrémentant.

    Donc oui au bout du bout du bout il y a une longue suite d'octets, mais pour toi, il n'y a qu'une suite de types (tu es en int, tu auras des int, tu es en double, tu auras des doubles) car malgré sa nature "bas niveau", le C possède quand-même une abstraction suffisante pour adapter son travail aux éléments que tu utilises.

    Citation Envoyé par primusG Voir le message
    cette différence de vue, implique la création d'un tableau et éventuellement de drapeaux dans les cases de ce tableau pour indiquer que les cases sont occupées
    Ah ça évidemment. Quand tu écris float x=0.123 puis float y=0.456, il ne faut pas que les variables "x" et "y" soient sur la même case, ça c'est évident. Comment fait-il pour s'assurer que telle case (ou telle suite de 4 cases) est libre et telle suite ne l'est pas, ça reste du détail interne. Mais les deux illustrations n'impactent en rien ce détail (dans la première illustration aussi, il ne faudrait pas qu'une variable "m" vienne se foutre sur la même case que "n")...
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  4. #4
    Membre à l'essai
    Homme Profil pro
    autre
    Inscrit en
    Octobre 2018
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : autre

    Informations forums :
    Inscription : Octobre 2018
    Messages : 30
    Points : 21
    Points
    21
    Par défaut
    Bonjour,

    tout d'abord, merci pour vos réponses.

    je me rends compte, à la lumière de vos commentaires, que j'ai oublié certaines explications qui, dans mon esprit, "aller de soi".
    tout d'abord, et s'est à mettre en lien avec: @Sve@r
    ça reste du détail interne
    mon questionnement ne s'arrête pas à la "surface" du language C, c'est-à-dire à l'usage du langage mais à son fonctionnement interne.
    Nom : wntgd.jpg
Affichages : 1182
Taille : 14,0 Ko

    je vais donner un exemple que j'espère correct: la fonction printf.
    en tant que débutant, une fois l'include fait on utilise cette fonction sans autre connaissance.
    Avec un peu de connaissance en compilation, sauf erreur de ma part, on apprend que le compilateur remplace purement et simplement l'appel à la fonction par le code de la fonction.

    Ce sont ces aspects sous-jacents à l'usage mais propre au fonctionnement interne qui me pose question.
    Pour poursuivre je reprends la question qui se pose à la fin @Sve@r
    Comment fait-il pour s'assurer que telle case (ou telle suite de 4 cases) est libre et telle suite ne l'est pas.
    C'est là LA question.
    De cette question viennent les 2 "modèles" que j'ai représentés et que j'aurai pu résumer ainsi:
    la cohérence des données, en mémoire, est elle portée par chaque case mémoire ou par un adressage externe.
    Pour illustre, je citerai 2 modèles (corrigez-moi si je me trompe);
    + la FAT qui fragmente le disque dur, avec en début une "table des matières" où sont référencés chaque cluster et le cluster qui ne contiens que les informations
    + le paquet IP qui, par un ajout de couche successive, contient l'ensemble des informations à son acheminement.

    Mon questionnement s'applique donc plus à la conception et aux choix qui on étaient faits l'or de la création du langage.

    De la réponse de foetus si ce n'est pas sous forme tabulaire serait-ce donc que la case mémoire porte l'ensemble des informations.
    maintenant que le choix est arrêté me viens une question à propos de la cohérence (c'est peut-être cette question que je n'arrivais pas à formuler).

    Dans le cas d'un float (4 octets, 32 bits) repartie sur 4 cases mémoire, si c'est la 1er case mémoire qui "porte" le type, un float n'aurait que 2^31 bits de valeurs.
    Or dans la littérature les floats ont 2^32 valeurs.

    Mais où est donc écrit l'information à propos du type contenu dans chaque case/ensemble de cases.
    même question pour l'adresse, si elle est "porter" par la case mémoire cette valeur empiète sur la plage de valeur possible du float.


    Une possibilité aurai été, une case mémoire est constituée d'une "pre-case" de taille fixe, contenant le type. Cela me parait peu probable vu la perte de place occasionner.
    dans le cas d'un float:
    Nom : float.png
Affichages : 1179
Taille : 8,5 Ko
    Car ces choix/concepts, en tant qu'utilisateur ne me sont pas connus. D'ailleurs quelqu'un sait-il ou trouver ces informations, si toutefois elles sont disponibles?

    PS: sur wikipedia les valeurs signées ont toutes pour plages [-a ; a]. C'est moi qui me trompe où il manque la place pour ce pauvre zéro ?

    PSS: je n'ai pas compris l'explication de foetus.
    pour moi "foo" signifier la valeur aléatoire contenu, j'aurai du mettre rand.

  5. #5
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par primusG Voir le message
    on apprend que le compilateur remplace purement et simplement l'appel à la fonction par le code de la fonction.
    Oui et non tu as la notion de macro qui sont juste 1 "morceau de code" à copier-coller pendant la phase de post compilation.

    Ensuite en C++ (et sûrement en C), si le compilateur "juge que la fonction est courte" oui il va remplacer l'appel par la fonction. Sinon non.
    En C++, il y a le mot clef inline qui est 1 conseil au compilateur (il peut l'ignorer).


    Citation Envoyé par primusG Voir le message
    Mais où est donc écrit l'information à propos du type contenu dans chaque case/ensemble de cases..
    Nulle part c'est la difficulté du C : c'est au développeur de savoir ce qu'il manipule et de "trimbaler" 1 pointeur sur la variable pour la modifier.
    Dans ma représentation, 1 variable commence à 1 adresse mémoire et le type permet de savoir sur combien d'octets (1 case = 1 octet)


    Citation Envoyé par primusG Voir le message
    pour moi "foo" signifier la valeur aléatoire contenu, j'aurai du mettre rand.
    Effectivement je pensais que
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    int n = 63;
    int* foo = &n;

    Citation Envoyé par primusG Voir le message
    De la réponse de foetus si ce n'est pas sous forme tabulaire serait-ce donc que la case mémoire porte l'ensemble des informations.
    Je pensais à la pagination, au mode protégé. Je ne veux pas trop en parler parce que dans ta tête c'est très compliqué
    mode protégé, lien wikipedia en français

    Et effectivement @WhiteCrow a raison : la gestion mémoire varie/ [peut varier] d'1 architecture à 1 autre.

  6. #6
    Membre expérimenté
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Juillet 2020
    Messages
    352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : Juillet 2020
    Messages : 352
    Points : 1 376
    Points
    1 376
    Par défaut
    Citation Envoyé par primusG Voir le message
    Bonjour,

    tout d'abord, merci pour vos réponses.

    je me rends compte, à la lumière de vos commentaires, que j'ai oublié certaines explications qui, dans mon esprit, "aller de soi".
    tout d'abord, et s'est à mettre en lien avec: @Sve@r
    mon questionnement ne s'arrête pas à la "surface" du language C, c'est-à-dire à l'usage du langage mais à son fonctionnement interne.
    Nom : wntgd.jpg
Affichages : 1182
Taille : 14,0 Ko

    je vais donner un exemple que j'espère correct: la fonction printf.
    en tant que débutant, une fois l'include fait on utilise cette fonction sans autre connaissance.
    Avec un peu de connaissance en compilation, sauf erreur de ma part, on apprend que le compilateur remplace purement et simplement l'appel à la fonction par le code de la fonction.
    Bonjour,

    Alors il y a deux chose qu'il ne faut absolument pas confondre :

    1. le modèle mémoire que le standard C définit ;
    2. le modèle mémoire d'une implémentation particulière pour une architecture particulière.



    C définit un modèle mémoire simple, très générique (tout objet non déclaré avec le qualificateur register possède une adresse, tout objet a une taille mémoire précise connue au pire au runtime qui dépend de son type, le type char est particulier et on a toujours sizeof(char)=1, etc.).
    Une architecture particulière (linux-64, sur un processeur intel particulier, avec une taille mémoire particulière par exemple) gèrera la mémoire différemment d'une autre.

    Ensuite, un header particulier propose (en général) un ensemble de déclaration permettant au compilateur de vérifier qu'une fonction est bien utilisée comme elle devrait l'être (en très gros).

    Mais surtout … un compilateur n'est pas un bête traducteur bête mot à mot (nous ne sommes plus dans les années 70…). Les compilos sont de plus en plus capables de produire un code plus performant. Par exemple, un appel de fonction pourra être in liné (le code de la fonction est carrément remplacé par l'appel), aboutir à un appel (classique), ou être remplacé par quelque chose de plus efficace si rien d'autre ne change (un print remplacé par un puts, mais c'est sur ce point qu'ils deviennent de plus en plus smart … si ça t'intéresse je te donnerai un exemple étonnant).


    Citation Envoyé par primusG Voir le message
    Ce sont ces aspects sous-jacents à l'usage mais propre au fonctionnement interne qui me pose question.
    Pour poursuivre je reprends la question qui se pose à la fin @Sve@r C'est là LA question.
    De cette question viennent les 2 "modèles" que j'ai représentés et que j'aurai pu résumer ainsi:
    la cohérence des données, en mémoire, est elle portée par chaque case mémoire ou par un adressage externe.
    Pour illustre, je citerai 2 modèles (corrigez-moi si je me trompe);
    + la FAT qui fragmente le disque dur, avec en début une "table des matières" où sont référencés chaque cluster et le cluster qui ne contiens que les informations
    + le paquet IP qui, par un ajout de couche successive, contient l'ensemble des informations à son acheminement.

    Mon questionnement s'applique donc plus à la conception et aux choix qui on étaient faits l'or de la création du langage.
    Un langage peut faire le choix d'une représentation de la mémoire (si il en permet la manipulation) ou carrément s'en désintéresser.
    Une implémentation de compilateur adapte cette vision à un cas réel ( un compilateur C pour intel comme target n'aura pas la même gueule qu'un compilateur pour un 6502 comme target).
    Mais on s'adapte toujours à l'architecture matérielle qu'on vise.

    Citation Envoyé par primusG Voir le message
    De la réponse de foetus si ce n'est pas sous forme tabulaire serait-ce donc que la case mémoire porte l'ensemble des informations.
    maintenant que le choix est arrêté me viens une question à propos de la cohérence (c'est peut-être cette question que je n'arrivais pas à formuler).

    Dans le cas d'un float (4 octets, 32 bits) repartie sur 4 cases mémoire, si c'est la 1er case mémoire qui "porte" le type, un float n'aurait que 2^31 bits de valeurs.
    Or dans la littérature les floats ont 2^32 valeurs.

    Mais où est donc écrit l'information à propos du type contenu dans chaque case/ensemble de cases.
    même question pour l'adresse, si elle est "porter" par la case mémoire cette valeur empiète sur la plage de valeur possible du float.


    Une possibilité aurai été, une case mémoire est constituée d'une "pre-case" de taille fixe, contenant le type. Cela me parait peu probable vu la perte de place occasionner.
    dans le cas d'un float:
    Nom : float.png
Affichages : 1179
Taille : 8,5 Ko
    Car ces choix/concepts, en tant qu'utilisateur ne me sont pas connus. D'ailleurs quelqu'un sait-il ou trouver ces informations, si toutefois elles sont disponibles?

    PS: sur wikipedia les valeurs signées ont toutes pour plages [-a ; a]. C'est moi qui me trompe où il manque la place pour ce pauvre zéro ?

    PSS: je n'ai pas compris l'explication de foetus.
    pour moi "foo" signifier la valeur aléatoire contenu, j'aurai du mettre rand.
    À nouveau le standard C impose des minima à respecter, comme le type int qui doit pouvoir a minima pouvoir représenter les entiers de [-32767, 32767]. Après l'implémentation peut faire ce qu'elle veut à partir du moment que ces minima sont respectés.
    Si l'implémentation décide d'utiliser une représentation bit signe + mantisse alors 0 aura deux représentations, si on en sur du complément à deux alors on aura souvent une valeur <0 de plus que les valeurs>0, 0 n'aura qu'une représentation.

  7. #7
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Une question similaire a été posée sur SO :

    https://stackoverflow.com/questions/...ps-track-of-it

    La norme du langage C ne dicte pas comment les types sont gérés ou attribués en mémoire, et le langage ne prévoit pas un "marquage" de type qui serait affecté à tel ou tel emplacement mémoire.

    La tâche de gérer les types selon le code écrit par le programmeur et les règles du langage est assurée par le compilateur et dépend donc de l'implémentation du langage C par tel ou tel compilateur. Une fois le code compilé, le langage machine produit gère, en dur, les éléments de mémoire selon cette logique fixée par le compilateur et tu n'as plus de notion de type qui intervient. C'est juste des bytes, words, qui sont manipulés par le processeur.

  8. #8
    Membre à l'essai
    Homme Profil pro
    autre
    Inscrit en
    Octobre 2018
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : autre

    Informations forums :
    Inscription : Octobre 2018
    Messages : 30
    Points : 21
    Points
    21
    Par défaut
    merci pour vos réponses.
    ce faisant tard je fatigue un poils donc pour le moment il m'apparait à la lumière de vos reposes que le C est un cahier des charges, qui peut etres +/- suivi tant pour l’implémentation que pour la compilation.
    pour le reste, pour le moment:
    Nom : et-les-shadoks-pompaient-pompaient-pompaient.73601.gif
Affichages : 1175
Taille : 250,2 Ko
    PS:
    parce que dans ta tête c'est très compliqué
    What ?

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par primusG Voir le message
    C est un cahier des charges, qui peut etres +/- suivi tant pour l’implémentation que pour la compilation.
    Non tu n'as pas compris
    Le C est normé et les normes (C89, C99, C11, C17, ... ANSI-C) décrivent les types, les mot clefs, ...

    Par exemple la norme dit que 1 char c'est 1 octet, 1 entier c'est à minima [-32767, 32767], que static c'est pour avoir 1 variable unique pendant toute la durée de vie, ...

    Ensuite le compilateur/ éditeur de lien doivent respecter la norme mais la norme C ne dit pas comment implémenter les choses.
    Par exemple, tu as 1 libC, la bibliothèque standard C, pour chaque plateforme.


    Citation Envoyé par primusG Voir le message
    PS: What ?
    Bien c'est compliqué pour toi
    Ton sujet de base c'est "la conceptualisation de la représentation des données en mémoire", mais en gros on s'en tape de comment en dessous tout cela fonctionne.

    Au niveau mémoire, on sait juste qu'il y a 1 pile ("stack", pour les variables locales et l'appel des fonctions avec les paramètres) et 1 tas ("heap", pour l'allocaton dynamique)

  10. #10
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par primusG Voir le message
    Mais où est donc écrit l'information à propos du type contenu dans chaque case/ensemble de cases.
    Dans ton code. Si tu écris float f=3.1416 tu dis explicitement "f est un float". Le compilo "sait" alors que pour récupérer "f", il faut récupérer un float donc récupérer 4 octets à partir de l'adresse de départ de "f' et créera le code assembleur en conséquence.
    Et (encore plus magique), si tu écris long l=123456, le truc récupère là aussi 4 octets mais comme le codage "float" et "long" n'est pas le même, adapte alors son travail en conséquence.
    Un exemple
    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <stdio.h>
     
    typedef union {
    	long l;
    	float f;
    } t_fou;
     
    int main() {
    	t_fou fou;
     
    	fou.f=3.1415927;
    	printf("f via l nature: %f\n", fou.l);
    	printf("f via l comme il faut: %f\n", *(float*)&fou.l);
     
    	fou.l=12345678;
    	printf("l via f nature: %ld\n", fou.f);
    	printf("l via f comme il faut: %ld\n", *(long*)&fou.f);
    }
    Pour le premier cas, je commence par afficher la zone mémoire mal décodée (décodage normalisation "long" de fou.l puisque c'est en effet un long, alors que cette zone contient un float).
    Puis je force le compilateur à considérer cette zone comme contenant un float (ce qui est réellement le cas) en lui disant "nan nan, cette adresse est celle d'un float donc lis-moi son contenu comme un float".
    Et similairement mais tout à l'opposé pour le second cas.

    PS: au cas où tu ne le saurais pas, une union est une structure où tous ses membres occupent la même zone mémoire (donc "f" et "l" sont placés au même endroit, chaque affectation réécrivant donc sur le même emplacement mémoire)
    PS2: dans une union, il est interdit d'écrire sur un des membres et d'aller lire ensuite un autre membre, c'est un comportement indéterminé. Mais c'était nécessaire ici pour que tu comprennes l'idée (et bon, comportement indéterminé mais qui, chez-moi, fonctionne malgré les milliers de warnings )
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  11. #11
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Pour répondre à la première question, voici 1 petit programme "à l'arrache" qui correspond au premier cours de hacking
    Par contre :
    • le compilateur réordonne les variables (alignement) et donc la variable de type short se trouve en premier et la variable de type size_t en dernier chez moi
    • donc je suis obligé de chercher la + petite adresse pour trouver le point de départ.
    • sur 1 processeur x86, les nombres sont stockés en mémoire en "little-endian" (le nombre est à l'envers)
    • pour afficher 1 octet en hexadecimal, il faut prendre les 4 bits les + à gauche (((*p) >> 4) & 0x0f) (poids fort) et ensuite les 4 bits les + à droite ((*p) & 0x0f) (poids faible)
    • les valeurs des variables sont en hexadecimal pour ne pas avoir de conversion à faire et voir la valeur directement.


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #include <stdio.h>
    #include <stdlib.h>
     
     
    int main()
    {
        int    a=0xa1a2a3a4;
        short  b=0xb1b2;
        size_t c=0xfedcba0987654321;
        int    d=0xc1c2c3c4;
     
        char* p;
        size_t nb_bytes, byte;
        char val;
     
        printf("All variables:\n--------------\n");
     
        printf("a : %#X         (at address %p)\n", a, &a);
        printf("b : %#hX             (at address %p)\n", b, &b);
        printf("c : %#lX (at address %p)\n", c, &c);
        printf("d : %#X         (at address %p)\n", d, &d);
     
        printf("\n\nIn memory:\n----------\n");
     
        nb_bytes = (sizeof(a) + sizeof(b) + sizeof(c) + sizeof(d));
     
    //  Find the first variable in memory, min address
        p = (char*) &a;
     
        if (p > (char*) (&b)) { p = (char*) &b; }
        if (p > (char*) (&c)) { p = (char*) &c; }
        if (p > (char*) (&d)) { p = (char*) &d; }
     
    //  Display memory
        for(byte=0; byte < nb_bytes; ++byte, ++p) {
            val = (((*p) >> 4) & 0x0f);
     
            printf("0x%c", ((val < 10)? ('0' + val): ('A' + (val - 10))));
     
            val = ((*p) & 0x0f);
     
            printf("%c ", ((val < 10)? ('0' + val): ('A' + (val - 10))));
        }
     
     
        return EXIT_SUCCESS;
    }
    La sortie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    All variables:
    --------------
    a : 0XA1A2A3A4         (at address 0x7ffebdc26950)
    b : 0XB1B2             (at address 0x7ffebdc2694e)
    c : 0XFEDCBA0987654321 (at address 0x7ffebdc26958)
    d : 0XC1C2C3C4         (at address 0x7ffebdc26954)
     
     
    In memory:
    ----------
    0xB2 0xB1 0xA4 0xA3 0xA2 0xA1 0xC4 0xC3 0xC2 0xC1 0x21 0x43 0x65 0x87 0x09 0xBA 0xDC 0xFE
    Et pour reprendre mon premier message :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    | 0x7ffebdc2694c |
    | 0x7ffebdc2694d |
    | 0x7ffebdc2694e | -> b: 0XB1B2
    | 0x7ffebdc2694f |
    | 0x7ffebdc26950 | -> a: 0XA1A2A3A4
    | 0x7ffebdc26951 |
    | 0x7ffebdc26952 |
    | 0x7ffebdc26953 |
    | 0x7ffebdc26954 | -> d: 0XC1C2C3C4
    | 0x7ffebdc26955 |
    | 0x7ffebdc26956 |
    | 0x7ffebdc26957 |
    | 0x7ffebdc26958 | -> c: 0XFEDCBA0987654321
    | 0x7ffebdc26959 |
    | 0x7ffebdc2695A |
    | 0x7ffebdc2695B |
    | 0x7ffebdc2695C |
    | 0x7ffebdc2695D |
    | 0x7ffebdc2695E |
    | 0x7ffebdc2695F |

  12. #12
    Membre expérimenté
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Juillet 2020
    Messages
    352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : Juillet 2020
    Messages : 352
    Points : 1 376
    Points
    1 376
    Par défaut
    Citation Envoyé par primusG Voir le message
    merci pour vos réponses.
    ce faisant tard je fatigue un poils donc pour le moment il m'apparait à la lumière de vos reposes que le C est un cahier des charges, qui peut etres +/- suivi tant pour l’implémentation que pour la compilation.
    pour le reste, pour le moment:
    Nom : et-les-shadoks-pompaient-pompaient-pompaient.73601.gif
Affichages : 1175
Taille : 250,2 Ko
    PS: What ?
    Ce qu'on appelle communément «Standard C» ou «Norme C» est décrit par plusieurs versions de documents. La norme la plus récente est le C17 (mais c'est du C11 à peine modifié), la prochaine sera C23 (une version plus remaniée de C11).
    Ces documents décrive une norme, c'est à dire un ensemble de règles plus ou moins strictes à suivre pour créer une implémentation de C.
    Note bien que je ne parle pas de «compilateur» C mais bien de C tout court. Le «C tout court» est d'une part la description du langage, du modèle mémoire, des comportements, … et d'autre part de la bibliothèque C standard. Le C est en gros l'union des deux.

    Un compilateur validé comme C11 ne peut l'être que sur une plateforme disposant d'une libc validée C11.

    Je ne pense pas que les détails de la norme soient réellement important à étudier en profondeur pendant la phase d'apprentissage. Mais il faut savoir que ces documents existent et qu'en quelque sorte ils font foi.
    Ce qui est important est de ne pas essayer d'apprendre les C historiques (K&R, C89), de se souvenir que C99 c'est bien mais que ça a 23 ans. Il faut essayer d'avoir un environnement (lorsque cela est possible) C11, et bientôt C23.
    Malheureusement, la majorité des tutos, cours et codes sont en C89 et en C99 au mieux.

    Il faut également conserver dans un coin de sa tête que savoir coder c'est une chose mais que savoir programmer est bien plus utile.

  13. #13
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 439
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 439
    Points : 43 082
    Points
    43 082
    Par défaut
    Mais où est donc écrit l'information à propos du type contenu dans chaque case/ensemble de cases.
    Nulle part, mise à part dans ton fichier source C. Une fois ton code C traduit en assembleur, il travaille avec des adresses mémoires, et il charge un octet, un mot, un double mot (un mot (word)=2 octets->16 bits, un double mot (dword)=4 octets->32 bits, un quadruple mot (qword)->64 bits). Et les derniers CPU ne travaillent pas à l’échelle de l'octet par exemple. C''est le code généré par le compilateur qui charge la longueur qu'il faut depuis les adresses nécessaires.

    le typage des variables du C va permettre par exemple au compilateur de vérifier que tu ne fais pas une opération de calcul entre une valeur numérique et un caractère. Au niveau binaire, si on considère un entier sur un octet (type int8_t, l'int normal étant minimum sur 16 bits, mais pouvant dépendre de l'architecture si je me souviens bien), au niveau assembleur, rien ne t'empècherais de prendre les valeurs des deux octets et de les additionner, ce qui n'aurais pas de sens.
    autre exemple : additionner un int avec un float. le C va convertir l'int en float, faire l'addition, et si la variable devant accueillir le résultat est un int, convertir le résultat en int avec arrondi si nécessaire, ce qui si tu devais le faire en assembleur représente un travail conséquent.

    Mis à part la curiosité, tu n'as pas besoin de savoir cela.

    ce n'est pas inutile non plus. Ça peut te permettre de comprendre pourquoi un logiciel crashe sur un pointeur non initialisé ou suite à un accès sur une zone mémoire libérée après un malloc. Ca peut te permettre de comprendre l'arithmétique signé, les dangers de fonctions comme strcpy, scanf mal utilisées.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  14. #14
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Du point de vue d'un compilateur C traditionnel, une variable locale est définie par son décalage vis-a-vis du pointeur de pile (ou plus souvent, par rapport à la valeur qu'avait le pointeur de pile au début de la fonction: Les plate-formes comme le x86 ont carrément un registre prévu pour y sauvegarder ce pointeur de pile: Le registre BP (16 bits), EBP (32 bits) ou RBP (64 bits)).

    En utilisant 32 bits pour mon exemple, le "prologue" traditionnel d'une fonction en C sur x86 c'est:
    • Au début, ESP (le pointeur de pile) pointe sur l'adresse de retour. Juste au-dessus (ESP+4) est le premier paramètre de la fonction.
    • On empile la valeur actuelle de EBP (donc on diminue ESP de 4, le premier paramètre est désormais à ESP+8)
    • On copie la valeur actuelle de ESP dans EBP
    • On soustrait à ESP la taille cumulée des variables locales (qu'on va appeler N).
    • On a donc:
      • les variables locales dans la zone de EBP-N (inclus) à EBP (exclu)
      • L'ancienne valeur de EBP à l'adresse EBP+0
      • L'adresse de retour à l'adresse EBP+4
      • Les paramètres à partir de EBP+8

    Bien sûr, c'est l'approche "théorique" et "naïve" sur une architecture désormais vieille: Le véritable arrangement des variables peut être différent en raison des sauvegardes de registres non-"jetables", des valeurs insérées pour détecter les débordements de tableaux, des variables dont le compilateur décide de ne pas les stocker en RAM, etc.

    Mais en règle générale, les instructions générées par le compilateur (hors optimisation) manipuleront ces variables via leur addresse en EBP±x

    Citation Envoyé par primusG Voir le message
    pour moi "foo" signifier la valeur aléatoire contenu, j'aurai du mettre rand.
    Attention, il ne faut pas confondre "inconnu"/"non-initialisé" et "aléatoire", vu qu'en fait tu n'as aucune garantie; la seule chose que tu aurais dû marquer dans cette case, c'est "??"
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  15. #15
    Membre à l'essai
    Homme Profil pro
    autre
    Inscrit en
    Octobre 2018
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : autre

    Informations forums :
    Inscription : Octobre 2018
    Messages : 30
    Points : 21
    Points
    21
    Par défaut
    Citation Envoyé par foetus Voir le message
    si le compilateur "juge que la fonction est courte" oui il va remplacer l'appel par la fonction. Sinon non.
    Citation Envoyé par WhiteCrow Voir le message
    un appel de fonction pourra être in liné (le code de la fonction est carrément remplacé par l'appel), aboutir à un appel (classique), ou être remplacé par quelque chose de plus efficace si rien d'autre ne change.
    donc il y a trois cas envisageables l'or d'un appel de fonctions.
    En quoi un appel de fonctions est plus efficace que le remplacement dans le code source par la définition de la fonction avec les paramètres à appliquer ?


    Citation Envoyé par WhiteCrow Voir le message
    quelque chose de plus efficace si rien d'autre ne change.
    C'est à dire ?


    Citation Envoyé par -Eks- Voir le message
    Une question similaire a été posée sur SO
    merci, en effets la ressemble de la question me rend moins seul.


    Citation Envoyé par Sve@r Voir le message
    PS: au cas où tu ne le saurais pas, une union est une structure où tous ses membres occupent la même zone mémoire (donc "f" et "l" sont placés au même endroit, chaque affectation réécrivant donc sur le même emplacement mémoire)
    PS2: dans une union, il est interdit d'écrire sur un des membres et d'aller lire ensuite un autre membre, c'est un comportement indéterminé. Mais c'était nécessaire ici pour que tu comprennes l'idée (et bon, comportement indéterminé mais qui, chez-moi, fonctionne malgré les milliers de warnings )
    m'étant limité aux struct c'est une découverte. Vaut-il mieux que je pose mes questions sur les unions ici ou que j'ouvre un autre sujet ?

    Citation Envoyé par chrtophe Voir le message
    les derniers CPU ne travaillent pas à l’échelle de l'octet par exemple
    ah ? pour quelle raison un tel changement ? où ou comment apprend-on de telle information ?

    Citation Envoyé par chrtophe Voir le message
    Nulle part, mise à part dans ton fichier source C. Une fois ton code C traduit en assembleur, il travaille avec des adresses mémoires, et il charge un octet, un mot, un double mot (un mot (word)=2 octets->16 bits, un double mot (dword)=4 octets->32 bits, un quadruple mot (qword)->64 bits)
    ok, il charge l'adresse mémoire "d'un mot" (quelconque) mais l'adresse mémoire est le début d'une zone. Comment est connue la longueur ? est-ce 16 bits ou 32 qui doivent être chargés car cette info n'est pas fournie dans le binaire, non ?
    j'espère que vous pardonnerez la stupidité de mes 2 prochaines questions:
    dans
    Citation Envoyé par chrtophe Voir le message
    il travaille avec des adresses mémoires
    on parle du CPU, non ? et donc par charger il me faut comprendre, mettre une valeur contenue dans la RAM dans un des registres du CPU ?

    Citation Envoyé par chrtophe Voir le message
    l'int normal étant minimum sur 16 bits, mais pouvant dépendre de l'architecture si je me souviens bien
    amusant, quand je me suis remis à l'informatique il y a de cela +/- 1 mois, et que j'ai cherché à accéder aux infos hardware, sujet auquel vous avez apporter vos contribution. Par extension lors de mes recherches, j'en suis venu à me demander sur combien de bit était coder les types (en premier lieu les int), je me suis demandé si la nature du CPU (32 ou 64 bits) et/ou de l'OS (8/16/32/64 bits) influer. Il en est ressorti que pour les CPU c'est un argument marketing et pour l'OS, la différence de bits permet un adressage plus long donc plus de mémoire (en gros).

    Citation Envoyé par chrtophe Voir le message
    Mis à part la curiosité, tu n'as pas besoin de savoir cela.
    ce n'est que ça. Si je peux comprendre le fonctionnement sous-jacent, cela m'enrichis et si en plus, par "effets de bord", j’étends encore un peu plus mes horizons/connaissances, c'est du bonus.

    Citation Envoyé par chrtophe Voir le message
    Ça peut te permettre de comprendre pourquoi un logiciel crashe sur un pointeur non initialisé ou suite à un accès sur une zone mémoire libérée après un malloc.
    cela n'a peut-être rien à voir mais si je reprends mon exemple avec int tab[5]. Est-ce l'OS qui me déclare un seg. fault si je fais int a = tab[5] ?
    car c'est l'OS le "gardien" de la mémoire. Or si je déclare un tableau de 5 int, et si tab = 0xblabla, le cinquième élément se situe à 5 * sizeof(int) + 0xblabla et tab[5] est équivalent à 6 * sizeof(int) + 0xblabla. Mais pour ce faire il faut connaitre le type ou la "longueur" des cases du tableau (sans même évoquer où se situe le nombre de cases constituant le tableau).

    PS:
    Citation Envoyé par foetus Voir le message
    Pour répondre à la première question, voici 1 petit programme "à l'arrache" qui correspond au premier cours de hacking
    ...
    j'aurai surement des questions une fois que j'aurais mieux compris votre explication
    Citation Envoyé par Médinoc Voir le message
    Du point de vue d'un compilateur C traditionnel, ...
    idem avec des dessins
    PSS:
    Citation Envoyé par Médinoc Voir le message
    Attention, il ne faut pas confondre "inconnu"/"non-initialisé" et "aléatoire", vu qu'en fait tu n'as aucune garantie; la seule chose que tu aurais dû marquer dans cette case, c'est "??"
    je me référerai au cas où, à la creation d'un tableau (tab) d'int de 5 cases si je fais un affichage de tab[3] j'obtiendrai une valeur qui serai fonction de l'ancienne information ecrite dans cette zone memoire sauf si il y a eut ecriture de zero avant (encore que dans ce cas j'aurais 0 comme valeur afficher)

  16. #16
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par primusG Voir le message
    En quoi un appel de fonctions est plus efficace que le remplacement dans le code source par la définition de la fonction avec les paramètres à appliquer ?
    La taille de l'exécutable.
    Et ensuite l'appel de la fonction peut être dans 1 bibliothèque externe et donc être changé.

    Mais ce n'est pas la bonne question : lorsqu'on appelle 1 fonction, il y a 1 "changement de contexte" pour simplifier.
    Donc lorsque la fonction est triviale, courte, tu perds du "temps processeur" pour pas grand chose.


    Citation Envoyé par primusG Voir le message
    pour quelle raison un tel changement ? où ou comment apprend-on de telle information ?
    J'attends la réponse mais je pense que @chrtophe voulait dire qu'1 processeur 32 bits travaille avec 4 octets et 64 bits 8 octets (c'est le sizeof d'1 pointeur justement)


    Citation Envoyé par primusG Voir le message
    Par extension lors de mes recherches, j'en suis venu à me demander sur combien de bit était coder les types (en premier lieu les int), je me suis demandé si la nature du CPU (32 ou 64 bits) et/ou de l'OS (8/16/32/64 bits) influer. Il en est ressorti que pour les CPU c'est un argument marketing et pour l'OS, la différence de bits permet un adressage plus long donc plus de mémoire (en gros).
    Tu te trompes
    Nom : type_size.png
Affichages : 687
Taille : 43,1 Ko

    Mais tu as l'alignement (<- lien wikipedia en français)


    Citation Envoyé par primusG Voir le message
    Or si je déclare un tableau de 5 int, et si tab = 0xblabla, le cinquième élément se situe à 5 * sizeof(int) + 0xblabla et tab[5] est équivalent à 6 * sizeof(int) + 0xblabla.
    Tu te trompes le 5ième élément se trouve à : 0xblabla + 4 * sizeof(int) soit tab[4]
    Et tab[5] c'est : 0xblabla + 5 * sizeof(int)

    Les tableaux commencent à l'indice 0. D'ailleurs le nom du tableau est le début du tableau 0xblabla.

  17. #17
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par primusG Voir le message
    En quoi un appel de fonctions est plus efficace que le remplacement dans le code source par la définition de la fonction avec les paramètres à appliquer ?
    C'est exactement la question qui a fait réfléchir les concepteurs du C et qui, un jour (je crois que c'était pour la version 2011) ont offert la "fonction inline" (qui existait depuis C++). Tu définis et écris une fonction mais le compilateur, sentant qu'elle est assez légère, préfère la recoder à chaque fois qu'on l'appelle.

    Citation Envoyé par primusG Voir le message
    m'étant limité aux struct c'est une découverte. Vaut-il mieux que je pose mes questions sur les unions ici ou que j'ouvre un autre sujet ?
    Nouvelle question=nouveau sujet mais je vois pas trop quelle question on peut avoir dessus. Une union c'est exactement comme une struct, à un seul détail: on n'a accès qu'à un seul membre à la fois. Ca a été créé (je pense) à l'époque où la RAM était rare et chère. Personnellement, mis à part pour des exemples comme celui que je t'ai écrit, m'en suis jamais servi.
    J'ai parfois vu des trucs comme ceci
    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    typedef union {
    	int adr;
    	char octets[8];
    } t_ip;
    permettant de claquer les 4 octets d'une adresse IP dans un int (programmation réseau). Ce qui d'ailleurs aujourd'hui me fait me poser des questions relatives à l'interdiction d'écrire dans un membre X et d'aller lire le membre Y d'une union (info qui m'avait été donnée sur ce forum) mais bon, ça reste du détail.

    Citation Envoyé par primusG Voir le message
    j'en suis venu à me demander sur combien de bit était coder les types (en premier lieu les int), je me suis demandé si la nature du CPU (32 ou 64 bits) et/ou de l'OS (8/16/32/64 bits) influer. Il en est ressorti que pour les CPU c'est un argument marketing et pour l'OS, la différence de bits permet un adressage plus long donc plus de mémoire (en gros).
    La norme donne la taille minimale des types. Un char fait au minimum 8 bits. Un short en fait 16, un long 32 etc. Mais j'ai vu des architectures où le short en faisait 16, le int en faisait 24 et le long 32.

    Citation Envoyé par primusG Voir le message
    cela n'a peut-être rien à voir mais si je reprends mon exemple avec int tab[5]. Est-ce l'OS qui me déclare un seg. fault si je fais int a = tab[5] ?
    car c'est l'OS le "gardien" de la mémoire. Or si je déclare un tableau de 5 int, et si tab = 0xblabla, le cinquième élément se situe à 5 * sizeof(int) + 0xblabla et tab[5] est équivalent à 6 * sizeof(int) + 0xblabla. Mais pour ce faire il faut connaitre le type ou la "longueur" des cases du tableau (sans même évoquer où se situe le nombre de cases constituant le tableau).
    Non, il n'y a aucun gardien. Comme je l'ai dit, en C le programmeur sait ce qu'il fait (enfin il est censé le savoir). Tu écris int tab[5]; tab[123]=456 tu n'auras aucun souci de compilation (enfin si avec les compilateurs modernes avec la bonne option tu auras un warning mais ce sont eux qui te préviennent, pas le langage et si tu passes par un pointeur le compilateur n'a aucun moyen de voir que tu dépasses).
    Et comme je l'ai dit aussi, tu as alors un comportement indéterminé (appelé aussi UB pour Undefined Behavior) => tu ne peux absolument pas prévoir ce qui va se passer. Ca peut marcher, ça peut donner un segfault, ça peut marcher les lundis pairs et planter les lundis impairs, ça peut marcher les mois à 30 jours et planter chaque 31 des mois à 31 jours, ça peut même marcher jusqu'au jour où tu rajoutes printf("coucou\n") et là ça plante et là tu t'arraches les cheveux à essayer de comprendre pourquoi avec ça plante et sans ça ne plante pas. in-dé-ter-min-né, n'oublie jamais ce verbe quand tu fais du C.

    Citation Envoyé par primusG Voir le message
    je me référerai au cas où, à la creation d'un tableau (tab) d'int de 5 cases si je fais un affichage de tab[3] j'obtiendrai une valeur qui serai fonction de l'ancienne information ecrite dans cette zone memoire
    Oui aussi. Définir un tableau ne te garantit pas la remise à zéro de son contenu. Le C te donne 5 cases prises dans la mémoire, mais tu n'as pas le droit d'afficher tab[3] sans avoir écrit auparavant tab[3]=valeur_de_ton_choix. D'ailleurs de façon plus générale, il est interdit de lire une variable sans l'avoir préalablement remplie, cela n'a aucun sens en algo donc n'en a non plus aucun en C. Pour simplifier: tu ne peux (moralement) pas faire en C ce qui n'a pas de sens en logique algorithmique.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  18. #18
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 439
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 439
    Points : 43 082
    Points
    43 082
    Par défaut
    J'attends la réponse mais je pense que @chrtophe voulait dire qu'1 processeur 32 bits travaille avec 4 octets et 64 bits 8 octets
    Les derniers processeurs sont optimisés pour travailler sur des adresses 32 ou 64 bits selon le cas de figure.Utiliser des blocs inférieurs peuvent être couteux ou simplement pas autorisés. conception interne. Il me semble que x86_64, les adresses doivent être alignés sur des multiples de 4. Il me faudrait lire la doc. De toute façon le compilateur gère.

    Comment est connue la longueur ? est-ce 16 bits ou 32 qui doivent être chargés car cette info n'est pas fournie dans le binaire, non ?
    via les opcode utilisés.

    exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    mov ax,5      # chargement 16 bits
    mov eax,5    # chargement 32 bits
    mov rax,5    # chargement 64 bits
    j'en suis venu à me demander sur combien de bit était coder les types (en premier lieu les int), je me suis demandé si la nature du CPU (32 ou 64 bits) et/ou de l'OS (8/16/32/64 bits) influer
    c'est précisé dans les normes du langage, peu importe le CPU tant que tu fais pas de l'assembleur.

    Est-ce l'OS qui me déclare un seg. fault si je fais int a = tab[5] ?
    car c'est l'OS le "gardien" de la mémoire. Or si je déclare un tableau de 5 int, et si tab = 0xblabla, le cinquième élément se situe à 5 * sizeof(int) + 0xblabla et tab[5] est équivalent à 6 * sizeof(int) + 0xblabla. Mais pour ce faire il faut connaitre le type ou la "longueur" des cases du tableau (sans même évoquer où se situe le nombre de cases constituant le tableau).
    L'OS va te déclencher un segfault si ton appli essaye d'accéder à une zone mémoire qu'il n' a pas le droit. Ca va probablement être le cas si tu cherches à accéder à l'élement 10000 d'un tableau de 10 éléments, car tu va taper hors zone de ton programme. Si tu accèdes à l'élement 11 (qui n'existe pas) d'un tableau de 10 éléments, tu n'auras pas de segfault mais u, comportement inattendu car te retournera une valeur qui correspond à tout à fait autre chose, à moins que ton compilateur est gueulé en détectant l'abération, mais le C est pas fort pour ce genre de truc.

    En quoi un appel de fonctions est plus efficace que le remplacement dans le code source par la définition de la fonction avec les paramètres à appliquer ?
    Le fait de remplacer un appel de fonction dans du code va générer du code plus long et donc un fichier plus gros. Mais l’intérêt du compilateur n'est pas d'économiser la mémoire ou l'espace disque, mais de faire une exécution la plus rapide possible. Intégrer le contenu d'une fonction à la place de son appel évite le prologue et l'épilogue de celle-ci, tu évites la sauvegarde des registres et la création de la stack frame. Le compilateur le fait quand ça vaut le coup. Il prend en compte d'autres éléments qui seraient trop longs à évoquer ici (et que de toute façon je connais pas forcément).

    Ton code C est fait pour être human readable, le code binaire machine readable, qu'il fasse de façon différente de toi importe peu tant qu'il garantisse le résultat attendu, ce que fait un compilateur.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  19. #19
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    En quoi un appel de fonctions est plus efficace que le remplacement dans le code source par la définition de la fonction avec les paramètres à appliquer ?
    Si la fonction est appelée plusieurs fois et est plus longue qu'un appel, alors la recopier à chaque fois augmente la taille du code.
    Si cela l'augment au point où un code (ou bout de code) qui tenait entièrement en mémoire cache ne tient plus entièrement, le processeur est obligé de swapper entre la mémoire cache et des mémoires plus lentes...

    Un autre prérequis pour l'inlining est que le compilateur doit connaitre le contenu de la fonction au moment où il compile l'appel. Ce qui tend à nécessiter:
    • soit que le corps de la fonction soit dans la même unité de compilation (fichier source plus les fichiers inclus par celui-ci) que l'appel;
    • soit que ta chaîne d'outils supporte la link-time code generation (faire la génération de code lors de l'édition de liens plutôt que lors de la compilation) et que celle-ci soit activée.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  20. #20
    Membre à l'essai
    Homme Profil pro
    autre
    Inscrit en
    Octobre 2018
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : autre

    Informations forums :
    Inscription : Octobre 2018
    Messages : 30
    Points : 21
    Points
    21
    Par défaut
    Bonjour,

    merci pour vos réponses, je les "dépilerai" plus attentivement avec le temps. Néanmoins, en reprenant le sujet du début, j'ai lu avec un plus d'attention le sujet en lien posté sur StackOverflow (merci @-Eks-). Il en ressort que: "C doesn't keep track of types anymore after compilation". Donc, le compilateur, le temps de faire les différentes vérifications/analyses, tient compte des types, puis produit le binaire où seul sont contenus le symbole/nom et l'adresse mémoire associer. Après cette étape, si lors de l’exécution une commande dépasse la zone mémoire allouée, ce qui est possible car le type n’étant plus disponible et dons la longueur de la zone en question on tombe dans un comportement indéterminé.
    Il reste de cette explication, sous condition qu'elle soit correcte, une question que j'avais déjà posée mais pas sous la borne forme.

    Mais tout d'abord je souhaite m'assure d'une chose. Une fois le binaire/exécutable produit. Comment se passe son exécution ?
    Admettons que le binaire contient les ordres de l’exécution du programme(du type mettre valeur A dans registre E1 mettre B dans registre E2 mettre somme d'E1 et E2 dans la zone mémoire 0x0395). Quel logiciel/programme les lis et les exécutent ? Encore une fois je fais l’hypothèse d'un programme que je vais nommer EC. Donc EC vas exécuter les ordres un par un, lorsqu'il arrive sur l’équivalent binaire de il demande à l'OS l'usage des registres du CPU plus l'ordre pour faire l'addition puis que l'OS lui crée une zone mémoire (c) pour y mettre le résultat. De là j'ai 2 interrogations/incertitude:
    + tout d'abord, le type étant perdu à ce moment, le code binaire devra donc contenir la longueur de la zone mémoire.
    + Ma seconde interrogation (c'est ma question de départ) comment fait l'OS pour garder une trace du "propriétaire" de chaque zone mémoire.

Discussions similaires

  1. Réponses: 3
    Dernier message: 21/10/2008, 13h41
  2. Réponses: 2
    Dernier message: 21/09/2008, 17h21
  3. Actualisation d'une variable suite à son changement?
    Par Dev@lone dans le forum Général JavaScript
    Réponses: 3
    Dernier message: 06/05/2008, 09h56
  4. lire une variable du type pointeur
    Par timtima dans le forum Débuter
    Réponses: 6
    Dernier message: 16/12/2007, 14h16
  5. Réponses: 2
    Dernier message: 09/11/2007, 15h32

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo