Pourquoi devons-nous mentionner le type de données de la variable en C


19

Habituellement en C, nous devons indiquer à l'ordinateur le type de données dans la déclaration de variable. Par exemple, dans le programme suivant, je veux imprimer la somme de deux nombres à virgule flottante X et Y.

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

J'ai dû dire au compilateur le type de variable X.

  • Le compilateur ne peut-il pas déterminer le type de Xlui-même?

Oui, je peux le faire:

#define X 5.2

Je peux maintenant écrire mon programme sans dire au compilateur le type de X:

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

Nous voyons donc que le langage C a une sorte de fonctionnalité, à l'aide de laquelle il peut déterminer lui-même le type de données. Dans mon cas, il a déterminé qu'il Xest de type float.

  • Pourquoi devons-nous mentionner le type de données, lorsque nous déclarons quelque chose dans main ()? Pourquoi le compilateur ne peut-il pas déterminer seul le type de données d'une variable main()comme il le fait dans #define.

14
En fait, ces deux programmes ne sont pas équivalents, car ils peuvent donner des sorties légèrement différentes! 5.2est un double, donc le premier programme arrondit les doubles littéraux à la floatprécision, puis les ajoute comme des flottants, tandis que le second arrondit la double représentation de 5.1 à doubleet l'ajoute à la doublevaleur 5.2 en utilisant l' doubleaddition, puis arrondit le résultat de ce calcul à la floatprécision . Étant donné que l'arrondi se produit à différents endroits, le résultat peut différer. Ceci n'est qu'un exemple pour les types de variables affectant le comportement d'un programme par ailleurs identique.

12
Lorsque vous le faites #define X 5.2, Xn'est pas une variable, mais une constante, il est donc littéralement remplacé par un préprocesseur avec 5.2n'importe où que vous avez mentionné X. Vous ne pouvez pas réaffecter X.
scriptin

16
Juste une note: c'est une bénédiction et une malédiction. D'une part, vous devez taper quelques caractères alors que le compilateur aurait vraiment pu le faire pour vous (le C ++ autofait en fait ce que vous voulez). D'un autre côté, si vous pensez savoir ce que fait votre code et que vous avez réellement tapé autre chose, une frappe statique comme celle-ci détectera une erreur plus tôt, avant qu'elle ne devienne un énorme problème. Chaque langue établit un équilibre: typage statique, inférence de typage, typage dynamique. Pour certaines tâches, la frappe supplémentaire en vaut la peine. Pour d'autres, c'est un gaspillage.
Cort Ammon - Rétablir Monica

Apprenez Ocaml et / ou Haskell .... vous serez satisfait de leurs capacités d'inférence de type.
Basile Starynkevitch

Réponses:


46

Vous comparez des déclarations de variables à #defines, ce qui est incorrect. Avec a #define, vous créez un mappage entre un identifiant et un extrait de code source. Le préprocesseur C remplacera alors littéralement toutes les occurrences de cet identifiant par l'extrait fourni. L'écriture

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

finit par être la même chose pour le compilateur que l'écriture

int foos = 40 + 2 + 40 + 2 * 40 + 2;

Considérez-le comme un copier-coller automatisé.

De plus, les variables normales peuvent être réassignées, alors qu'une macro créée avec #definene peut pas (bien que vous puissiez la réutiliser #define). L'expression FOO = 7serait une erreur de compilation, car nous ne pouvons pas attribuer à «rvalues»: 40 + 2 = 7est illégal.

Alors, pourquoi avons-nous besoin de types? Certains langages se débarrassent apparemment des types, ceci est particulièrement courant dans les langages de script. Cependant, ils ont généralement quelque chose appelé «typage dynamique» où les variables n'ont pas de types fixes, mais les valeurs en ont. Bien que cela soit beaucoup plus flexible, il est également moins performant. C aime les performances, il a donc un concept de variables très simple et efficace:

Il y a une partie de la mémoire appelée «pile». Chaque variable locale correspond à une zone de la pile. Maintenant, la question est de combien d’octets cette zone doit-elle avoir? En C, chaque type a une taille bien définie que vous pouvez interroger via sizeof(type). Le compilateur a besoin de connaître le type de chaque variable pour pouvoir réserver la bonne quantité d'espace sur la pile.

Pourquoi les constantes créées avec n'ont-elles pas #definebesoin d'une annotation de type? Ils ne sont pas stockés sur la pile. Au lieu de cela, #definecrée des extraits de code source réutilisables d'une manière légèrement plus maintenable que le copier-coller. Les littéraux dans le code source tels que "foo"ou 42.87sont stockés par le compilateur soit en ligne sous forme d'instructions spéciales, soit dans une section de données distincte du binaire résultant.

Cependant, les littéraux ont des types. Un littéral de chaîne est un char *. 42est un intmais peut également être utilisé pour les types plus courts (rétrécissement de la conversion). 42.8serait un double. Si vous avez un littéral et que vous voulez qu'il ait un type différent (par exemple pour créer 42.8un floatou 42un unsigned long int), vous pouvez utiliser des suffixes - une lettre après le littéral qui change la façon dont le compilateur traite ce littéral. Dans notre cas, nous pourrions dire 42.8fou 42ul.

Certaines langues ont un typage statique comme en C, mais les annotations de type sont facultatives. Les exemples sont ML, Haskell, Scala, C #, C ++ 11 et Go. Comment ça marche? La magie? Non, cela s'appelle «inférence de type». En C # et Go, le compilateur examine le côté droit d'une affectation et en déduit le type. C'est assez simple si le côté droit est un littéral tel que 42ul. Il est alors évident quel devrait être le type de la variable. D'autres langages ont également des algorithmes plus complexes qui prennent en compte la façon dont une variable est utilisée. Par exemple, si vous le faites x/2, alors xne peut pas être une chaîne mais doit avoir un certain type numérique.


Merci d'avoir expliqué. Ce que je comprends, c'est que lorsque nous déclarons le type d'une variable (locale ou globale), nous disons en fait au compilateur, combien d'espace doit-il réserver pour cette variable dans la pile. D'un autre côté, #definenous avons une constante qui est directement convertie en code binaire - aussi longue soit-elle - et qui est stockée dans la mémoire telle quelle.
user106313

2
@ user31782 - pas tout à fait. Lorsque vous déclarez une variable, le type indique au compilateur les propriétés de la variable. L'une de ces propriétés est la taille; d'autres propriétés incluent la façon dont il représente les valeurs et quelles opérations peuvent être effectuées sur ces valeurs.
Pete Becker

@PeteBecker Alors, comment le compilateur connaît-il ces autres propriétés #define X 5.2?
user106313

1
En effet, en vous passant le mauvais type, printfvous avez invoqué un comportement non défini. Sur ma machine, cet extrait de code imprime une valeur différente à chaque fois, sur Ideone, il se bloque après avoir imprimé zéro.
Matteo Italia

4
@ user31782 - "Il semble que je puisse effectuer n'importe quelle opération sur n'importe quel type de données" Non. Ce X*Yn'est pas valide si Xet Ysont des pointeurs, mais c'est correct s'ils sont ints; *Xn'est pas valide si Xc'est un int, mais ça va si c'est un pointeur.
Pete Becker

4

X dans le deuxième exemple n'est jamais un flottant. Elle s'appelle une macro, elle remplace la valeur de macro définie «X» dans la source par la valeur. Un article lisible sur #define est ici .

Dans le cas du code fourni, avant la compilation, le préprocesseur change le code

Z=Y+X;

à

Z=Y+5.2;

et c'est ce qui est compilé.

Cela signifie que vous pouvez également remplacer ces «valeurs» par du code comme

#define X sqrt(Y)

ou même

#define X Y

3
Cela s'appelle simplement une macro, pas une macro variadique. Une macro variadique est une macro qui prend un nombre variable d'arguments, par exemple #define FOO(...) { __VA_ARGS__ }.
hvd

2
Mon mauvais, réparera :)
James Snell

1

La réponse courte est que C a besoin de types en raison de l'historique / représentant le matériel.

Historique: C a été développé au début des années 1970 et a été conçu comme un langage pour la programmation de systèmes. Le code est idéalement rapide et tire le meilleur parti des capacités du matériel.

L'inférence de types au moment de la compilation aurait été possible, mais les temps de compilation déjà lents auraient augmenté (voir le dessin animé de compilation de XKCD. Cela s'appliquait au `` bonjour le monde '' pendant au moins 10 ans après la publication de C ). L'inférence de types lors de l'exécution n'aurait pas correspondu aux objectifs de la programmation des systèmes. L'inférence à l'exécution nécessite une bibliothèque d'exécution supplémentaire. C est venu bien avant le premier PC. Qui avait 256 RAM. Pas des gigaoctets ou mégaoctets mais des kilo-octets.

Dans votre exemple, si vous omettez les types

   X=5.2;
   Y=5.1;

   Z=Y+X;

Ensuite, le compilateur aurait pu comprendre que X & Y sont des flottants et faire de Z la même chose. En fait, un compilateur moderne déterminerait également que X & Y ne sont pas nécessaires et définirait simplement Z à 10,3.

Supposons que le calcul soit intégré dans une fonction. L'auteur de la fonction peut vouloir utiliser ses connaissances du matériel ou résoudre le problème.

Un double serait-il plus approprié qu'un flotteur? Prend plus de mémoire et est plus lent mais la précision du résultat serait plus élevée.

Peut-être que la valeur de retour de la fonction pourrait être int (ou longue) car les décimales n'étaient pas importantes, bien que la conversion de float en int ne soit pas sans coût.

La valeur de retour pourrait également être rendue double en garantissant que flotteur + flotteur ne déborde pas.

Toutes ces questions semblent inutiles pour la grande majorité du code écrit aujourd'hui, mais étaient vitales lorsque C a été produit.


1
cela n'explique pas, par exemple, pourquoi les déclarations de type n'ont pas été rendues facultatives, permettant au programmeur de choisir de les déclarer explicitement ou de s'appuyer sur le compilateur pour déduire
gnat

1
En effet, ce n'est pas @gnat. J'ai modifié le texte, mais cela ne servait à rien de le faire à l'époque. Le domaine C a été conçu pour réellement vouloir décider de stocker 17 dans 1 octet, ou 2 octets ou 4 octets ou sous forme de chaîne ou de 5 bits dans un mot.
itj

0

C n'a pas d'inférence de type (c'est ce qu'on appelle quand un compilateur devine le type d'une variable pour vous) car il est ancien. Il a été développé au début des années 1970

De nombreux langages récents ont des systèmes qui vous permettent d'utiliser des variables sans spécifier leur type (ruby, javascript, python, etc.)


12
Aucun des langages que vous avez mentionnés (Ruby, JS, Python) n'a d'inférence de type comme fonctionnalité de langage, bien que les implémentations puissent l'utiliser pour augmenter l'efficacité. Au lieu de cela, ils utilisent la saisie dynamique lorsque les valeurs ont des types, mais pas les variables ou autres expressions.
amon

2
JS ne vous permet pas d' omettre le type - il ne vous permet tout simplement pas de le déclarer. Il utilise le typage dynamique, où les valeurs ont des types (par exemple, trueest boolean), pas des variables (par exemple, var xpeuvent contenir des valeurs de n'importe quel type). De plus, l'inférence de type pour des cas aussi simples que ceux de la question était probablement connue une décennie avant la publication de C.
scriptin

2
Cela ne rend pas la déclaration fausse (pour forcer quelque chose, vous devez également l'autoriser). L'inférence de type existante ne change pas le fait que le système de type de C est le résultat de son contexte historique (par opposition à un raisonnement philosophique ou à une limitation technique spécifiquement énoncé)
Tristan Burnside

2
Considérant que ML - qui est à peu près aussi vieux que C - a une inférence de type, "c'est vieux" n'est pas une bonne explication. Le contexte dans lequel C a été utilisé et développé (petites machines qui ont exigé une très petite empreinte pour le compilateur) semble plus probable. Je ne sais pas pourquoi vous mentionneriez des langages de frappe dynamiques au lieu de quelques exemples de langages avec inférence de type - Haskell, ML, diable C # l' a - à peine une fonction obscure plus.
Voo

2
@BradS. Fortran n'est pas un bon exemple car la première lettre du nom de variable est une déclaration de type, sauf si vous utilisez implicit nonedans ce cas, vous devez déclarer un type.
dmckee
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.