[C] Analyse d'un programme très simple

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je ne comprends pas très bien pourquoi la compilation du programme suivant échoue :

#include <stdio.h>

void test() {
    printf("i : %d\n", i);
}

int i; // Variable statique et globale

int main() {
    test();
    return 0;
}

test.c: In function ‘test’:
test.c:5:21: error: ‘i’ undeclared (first use in this function)
printf("i : %d\n", i);

En suivant l’exécution du programme ligne par ligne, la fonction devrait avoir accès à la variable i au moment de son exécution, c’est le cas par exemple en Python :

def test():
    print(i) # Affiche : 42

i = 42

def main():
    test()

main()

Qu’est-ce qui différence Python de C sur ce point ?

Salut,

Essaye de déclarer ta variable "i" avant la déclaration de ta méthode "test" ;)

Au moment de la compilation, la méthode ne connait pas encore "i"

Je ne connais pas du tout Python, mais c’est un langage interprété qui saute l’étape de compilation (à prendre avec des pincettes :D )

+1 -0

Au moment de la compilation, la méthode ne connait pas encore "i"

Ah, d’accord, du coup un programme C compile ligne par ligne, dans l’ordre spécifié dans le code source.
Du coup, pourquoi j’obtiens la même erreur ici ?

#include <stdio.h>

void test();

int main()
{
    static int i = 42;
    test();

    printf("main : %d\n", i);
    return 0;
}

void test()
{
    printf("i : %d\n", i);
}

Ligne 7, le programme connaît la variable i.
Pourquoi l’a-t-il oubliée ligne 16 alors que i est globale et statique ?

Parce que, dans ce second cas, elle est effectivement statique … Mais plus globale. En C, la portée des variables est limitée au bloc dans lequel elle est définie, sauf mention contraire :)

(quand à Python, je serais près à parier qu’il ne s’intéresse à interpréter1 la fonction qu’une fois qu’elle est effectivement appelée, d’où le fait que ça fonctionne)


  1. Ce qui ne l’empêche pas de l’avoir déjà parsée avant

Dans ce cas là, c’est un problème de porté ! Ta variable "i" est déclarée dans le main, et donc uniquement visible dans le main

Au passage, déclarer une variable statique dans le main n’a pas trop de sens .. ça revient à déclarer une variable normale (sauf qu’elle sera automatiquement initialisée à 0) :)

Au passage, déclarer une variable statique dans le main n’a pas trop de sens .. ça revient à déclarer une variable normale (sauf qu’elle sera automatiquement initialisée à 0) :)

Tonio

Nope nope nope.

Une variable globale est par défaut externe, et donc est déjà initialisée à 0 (ou équivalent suivant le type) si non initialiser autrement. Rajouter static change donc la manière dont elle est linkée uniquement.


Pour répondre à ta question Green, il faut se penser sur comment fonctionne Python et C.

Python est interprété. Grosso modo, il y a 2 étapes pour obtenir exécuter du code Python.

1) Parser le code. 2) Exécuter le code.

Dès la fin de la première étape, le code est parsé, python connait les tokens qui constituent le programme. En parsant ces tokens ils a vérifié en même temps la syntaxe du fichier.

À l’exécution, il va simplement suivre lire le programme et exécuter linéairement selon le flow d’exécution. Une déclaration est une instruction comme une autre. Un variable est déclarée uniquement quand le fils d’exécution arrive à la déclaration.

Pour le C c’est totalement différent.

En C, on compile.

Grosso modo encore une fois, car les étapes sont déjà nombreuses. Le compilateur va passer le préprocesseur qui va donc remplacer les #include<> et autres « instructions » du préprocesseur (les lignes qui commencent par #, les macros et quelques autres trucs). Le résultat est une sorte de fichier virtuel (chaque fichier d’origine devient un fichier virtuel). Le compilateur va alors lire ce fichier ligne par ligne, de la première à la dernière. Il va tenir à jour sa liste de définitions. Si un identifiant est utilisé alors qu’il n’est pas encore déclaré alors le compilateur va t’insulter. Quand il a fini, le compilateur à effectivement compiler le code. C’est-à-dire que le résultat est déjà quasiment exécutable. Mais pour pouvoir obtenir un binaire et lié les fichiers entre eux il a besoin d’un linker. C’est là qu’entre en jeu le linkage static ou extern d’une fonction ou d’une variable globale. Si le linkage est static alors l’identifiant est propre au fichier, s’il est extern alors il est partagé entre les fichiers. Le résultat du linkage est un binaire que l’on peut alors exécuter. L’exécution étant la dernière étape le fils d’exécution n’impacte pas la déclaration ou non d’une variable.

Et c’est là toute la différence entre les deux. 😅

+0 -0

Je ne connais pas du tout Python, mais c’est un langage interprété qui saute l’étape de compilation (à prendre avec des pincettes :D ) Source:Tonio

Pour répondre à ta question Green, il faut se penser sur comment fonctionne Python et C.

Python est interprété. Grosso modo, il y a 2 étapes pour obtenir exécuter du code Python.

ache

Ce n’est pas ce qui fait la différence. Déjà, l’implémentation Python de référence (CPython) procède via une étape de compilation en bytecode, et ce n’est pas une compilation vers du langage machine qui garantit quoi que ce soit sur la portée des variables.

Ce sont juste les sémantiques des deux langages qui diffèrent là-dessus dans leurs définitions de la portée.

Ce n’est pas ce qui fait la différence. Déjà, l’implémentation Python de référence (CPython) procède via une étape de compilation en bytecode, et ce n’est pas une compilation vers du langage machine qui garantit quoi que ce soit sur la portée des variables.

Le bytecode n’a peu ou pas d’importance, c’est pour ça que je n’ai pas voulu en parler. Et je n’ai absolument pas dit que la porté etait influencée par le fait que le C soit compilé. D’ailleurs, je n’ai absolument pas parler de portée des variables.

Ce sont juste les sémantiques des deux langages qui diffèrent là-dessus dans leurs définitions de la portée.

entwanne

Ce que j’essayais de dire. Peut-être maladroitement, c’est que les deux langages diffèrent par leur manière de définir une variable. La notion de portée dans les deux langages par-contre me semble assez similaire.

La définition d’une variable en Python se fait au runtime, un chemin rarement linéaire. Une définition en C se fait à la compilation, ligne par ligne.

+0 -0

La différence vient plutôt de la résolution du nom qui est tardive en Python, pas de la définition / déclaration.

Pour en revenir au code initial de l’OP par exemple, en regardant le bytecode Python (CPython 3.7) on remarque que la définition de i arrive avant les codes des fonctions, il ne s’agit que d’un détail d’implémentation.
Par contre les règles de portée assurent que ça se comporte bien ainsi.

La différence vient plutôt de la résolution du nom qui est tardive en Python, pas de la définition / déclaration.

Je ne comprends pas ce que tu dis. Pour moi résolution de nom, c’est le fait de retrouver la variable à partir d’un identifiant. La résolution est liée à la portée de l’identifiant et bien-sûr la résolution d’un identifiant qui n’existe pas encore conduit à une erreur. (« undefined »).

Je ne comprends pas ce que tu veux dire par « Résolution tardive ». Pour moi, c’est la définition qui est tardive, puisqu’elle est effectuée au runtime. ^^"

Pour en revenir au code initial de l’OP par exemple, en regardant le bytecode Python (CPython 3.7) on remarque que la définition de i arrive avant les codes des fonctions, il ne s’agit que d’un détail d’implémentation.

Pardon moi mais, comment lis-tu le byte code Python ? Tu as un utilitaire pour t’aider ou tu utilises simplement le module dis ? Je n’ai pas du tout étudié le bytecode CPython mais ça pourrait être intéressant de regarder.

+0 -0

Pour moi, c’est la définition qui est tardive, puisqu’elle est effectuée au runtime. ^^"

On s’en fout de ça, ce qui compte c’est que la résolution de nom soit faite après la définition. Et ça c’est le standard Python qui le demande, pas le fait qu’il soit interpété, ou compilé, et cela que la définition soit faite au runtime ou statiquement.

C’est là qu’est la différence entre C et Python. C demande à ce que les noms des variables dans une fonction soient résolus à la déclaration. Python demande à ce que les noms soit résolus à l’appel.

Pour moi, c’est la définition qui est tardive, puisqu’elle est effectuée au runtime. ^^"

On s’en fout de ça, ce qui compte c’est que la résolution de nom soit faite après la définition. Et ça c’est le standard Python qui le demande, pas le fait qu’il soit interpété, ou compilé, et cela que la définition soit faite au runtime ou statiquement.

C’est là qu’est la différence entre C et Python. C demande à ce que les noms des variables dans une fonction soient résolus à la déclaration. Python demande à ce que les noms soit résolus à l’appel.

adri1

Ça n’explicite toujours pas ce que vous entendez par résolution de nom. Wikipédia anglais m’a bien expliqué tout ça, mais j’ai encore un peu du mal à vous saisir.

Name resolution

La résolution de nom n’est pas une notion qu’on retrouve dans les normes du langage C.

C demande à ce que les noms des variables dans une fonction soient résolus à la déclaration.

Résolus à la déclaration ? Déclarer avant avant utilisation ? Je ne comprend pas ce que vous entendez par « les noms des variables dans une fonction soient résolus à la déclaration ».

+0 -0

Je ne comprends pas ce que tu dis. Pour moi résolution de nom, c’est le fait de retrouver la variable à partir d’un identifiant. La résolution est liée à la portée de l’identifiant et bien-sûr la résolution d’un identifiant qui n’existe pas encore conduit à une erreur. (« undefined »).

ache

C’est exactement ça. Mais en Python cette résolution est faite lorsque la fonction est appelée, il n’y a pas de mécanisme comme en C pour remplacer un nom de variable par un emplacement mémoire précis.

Je ne comprends pas ce que tu veux dire par « Résolution tardive ». Pour moi, c’est la définition qui est tardive, puisqu’elle est effectuée au runtime. ^^"

ache

La ligne où la variable est définie ne change rien, c’est son scope qui importe. Le comportement serait exactement le même si la variable était définie tout en haut du fichier. Dans les deux cas elle n’est résolue qu’à l’exécution de la fonction.

Pardon moi mais, comment lis-tu le byte code Python ? Tu as un utilitaire pour t’aider ou tu utilises simplement le module dis ? Je n’ai pas du tout étudié le bytecode CPython mais ça pourrait être intéressant de regarder.

ache

Il y a sûrement de meilleurs outils, je n’ai pas été plus loin que ça, mais oui j’ai simplement appelé dis.dis en lui donnant le contenu du fichier pyc.

Résolus à la déclaration ? Déclarer avant avant utilisation ? Je ne comprend pas ce que vous entendez par « les noms des variables dans une fonction soient résolus à la déclaration ».

ache

En C, le nom de variable n’est utile qu’à la définition des fonctions, pour savoir à quel emplacement mémoire on fait référence. Cette résolution nom ↔ emplacement mémoire est faite dès la définition, et la notion de nom n’a plus d’utilité ensuite.

En Python, la variable n’est retrouvée à partir de son nom qu’à l’exécution de la fonction. Il n’est donc pas utile que le nom soit connu à la définition.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte