C Language
Déclarations
Recherche…
Remarques
La déclaration d'identifiant faisant référence à un objet ou à une fonction est souvent désignée simplement comme une déclaration d'objet ou de fonction.
Appeler une fonction depuis un autre fichier C
foo.h
#ifndef FOO_DOT_H /* This is an "include guard" */
#define FOO_DOT_H /* prevents the file from being included twice. */
/* Including a header file twice causes all kinds */
/* of interesting problems.*/
/**
* This is a function declaration.
* It tells the compiler that the function exists somewhere.
*/
void foo(int id, char *name);
#endif /* FOO_DOT_H */
foo.c
#include "foo.h" /* Always include the header file that declares something
* in the C file that defines it. This makes sure that the
* declaration and definition are always in-sync. Put this
* header first in foo.c to ensure the header is self-contained.
*/
#include <stdio.h>
/**
* This is the function definition.
* It is the actual body of the function which was declared elsewhere.
*/
void foo(int id, char *name)
{
fprintf(stderr, "foo(%d, \"%s\");\n", id, name);
/* This will print how foo was called to stderr - standard error.
* e.g., foo(42, "Hi!") will print `foo(42, "Hi!")`
*/
}
principal c
#include "foo.h"
int main(void)
{
foo(42, "bar");
return 0;
}
Compiler et Lier
Tout d' abord, nous compilons les deux foo.c
et main.c
aux fichiers objet. Ici, nous utilisons le compilateur gcc
, votre compilateur peut avoir un nom différent et avoir besoin d'autres options.
$ gcc -Wall -c foo.c
$ gcc -Wall -c main.c
Maintenant, nous les lions ensemble pour produire notre exécutable final:
$ gcc -o testprogram foo.o main.o
Utilisation d'une variable globale
L'utilisation de variables globales est généralement déconseillée. Cela rend votre programme plus difficile à comprendre et plus difficile à déboguer. Mais parfois, l'utilisation d'une variable globale est acceptable.
global.h
#ifndef GLOBAL_DOT_H /* This is an "include guard" */
#define GLOBAL_DOT_H
/**
* This tells the compiler that g_myglobal exists somewhere.
* Without "extern", this would create a new variable named
* g_myglobal in _every file_ that included it. Don't miss this!
*/
extern int g_myglobal; /* _Declare_ g_myglobal, that is promise it will be _defined_ by
* some module. */
#endif /* GLOBAL_DOT_H */
global.c
#include "global.h" /* Always include the header file that declares something
* in the C file that defines it. This makes sure that the
* declaration and definition are always in-sync.
*/
int g_myglobal; /* _Define_ my_global. As living in global scope it gets initialised to 0
* on program start-up. */
principal c
#include "global.h"
int main(void)
{
g_myglobal = 42;
return 0;
}
Voir aussi Comment utiliser extern
pour partager des variables entre des fichiers sources?
Utiliser des constantes globales
Les en-têtes peuvent être utilisés pour déclarer des ressources en lecture seule utilisées globalement, comme les tables de chaînes par exemple.
Déclarez ceux d'un en-tête séparé qui est inclus par tout fichier (" Translation Unit ") qui veut les utiliser. Il est pratique d'utiliser le même en-tête pour déclarer une énumération associée afin d'identifier toutes les ressources de chaîne:
ressources.h:
#ifndef RESOURCES_H
#define RESOURCES_H
typedef enum { /* Define a type describing the possible valid resource IDs. */
RESOURCE_UNDEFINED = -1, /* To be used to initialise any EnumResourceID typed variable to be
marked as "not in use", "not in list", "undefined", wtf.
Will say un-initialised on application level, not on language level. Initialised uninitialised, so to say ;-)
Its like NULL for pointers ;-)*/
RESOURCE_UNKNOWN = 0, /* To be used if the application uses some resource ID,
for which we do not have a table entry defined, a fall back in
case we _need_ to display something, but do not find anything
appropriate. */
/* The following identify the resources we have defined: */
RESOURCE_OK,
RESOURCE_CANCEL,
RESOURCE_ABORT,
/* Insert more here. */
RESOURCE_MAX /* The maximum number of resources defined. */
} EnumResourceID;
extern const char * const resources[RESOURCE_MAX]; /* Declare, promise to anybody who includes
this, that at linkage-time this symbol will be around.
The 1st const guarantees the strings will not change,
the 2nd const guarantees the string-table entries
will never suddenly point somewhere else as set during
initialisation. */
#endif
Pour définir réellement les ressources créées dans un fichier .c associé, il s'agit d'une autre unité de traduction contenant les instances réelles de ce qui a été déclaré dans le fichier d'en-tête (.h) associé:
resources.c:
#include "resources.h" /* To make sure clashes between declaration and definition are
recognised by the compiler include the declaring header into
the implementing, defining translation unit (.c file).
/* Define the resources. Keep the promise made in resources.h. */
const char * const resources[RESOURCE_MAX] = {
"<unknown>",
"OK",
"Cancel",
"Abort"
};
Un programme utilisant ceci pourrait ressembler à ceci:
principal c:
#include <stdlib.h> /* for EXIT_SUCCESS */
#include <stdio.h>
#include "resources.h"
int main(void)
{
EnumResourceID resource_id = RESOURCE_UNDEFINED;
while ((++resource_id) < RESOURCE_MAX)
{
printf("resource ID: %d, resource: '%s'\n", resource_id, resources[resource_id]);
}
return EXIT_SUCCESS;
}
Compilez les trois fichiers ci-dessus en utilisant GCC, et liez-les pour devenir le fichier main
du programme, par exemple en utilisant ceci:
gcc -Wall -Wextra -pedantic -Wconversion -g main.c resources.c -o main
(utilisez ces -Wall -Wextra -pedantic -Wconversion
pour rendre le compilateur vraiment pointilleux, de sorte que vous ne manquiez de rien avant de poster le code dans SO, direz le monde entier, ou même le déployez en production)
Exécutez le programme créé:
$ ./main
Et obtenir:
resource ID: 0, resource: '<unknown>'
resource ID: 1, resource: 'OK'
resource ID: 2, resource: 'Cancel'
resource ID: 3, resource: 'Abort'
introduction
Exemple de déclaration:
int a; /* declaring single identifier of type int */
La déclaration ci-dessus déclare un identifiant unique nommé a
qui fait référence à un objet de type int
.
int a1, b1; /* declaring 2 identifiers of type int */
La seconde déclaration déclare 2 identificateurs nommés a1
et b1
qui font référence à d'autres objets avec le même type int
.
Fondamentalement, la façon dont cela fonctionne est comme ceci - d'abord vous mettez un type , puis vous écrivez une ou plusieurs expressions séparées par une virgule ( ,
qui ne seront pas évaluées à ce stade - et qui devraient autrement être qualifiées de déclarants dans ce contexte ). En écrivant de telles expressions, vous êtes autorisé à appliquer uniquement les opérateurs indirection ( *
), appel de fonction ( ( )
) ou indice (ou indexation de tableau - [ ]
) à un identifiant (vous ne pouvez également utiliser aucun opérateur). L'identifiant utilisé n'a pas besoin d'être visible dans la portée actuelle. Quelques exemples:
/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# | La description |
---|---|
1 | Le nom du type entier. |
2 | Expression non évaluée appliquant l'indirection à un identifiant z . |
3 | Nous avons une virgule indiquant qu'une expression de plus suivra dans la même déclaration. |
4 | Expression non évaluée appliquant l'indirection à un autre identifiant x . |
5 | Expression non évaluée appliquant l'indirection à la valeur de l'expression (*c) . |
6 | Fin de déclaration |
Notez qu'aucun des identificateurs ci-dessus n'était visible avant cette déclaration et que les expressions utilisées ne seraient donc pas valides avant.
Après chaque expression, l'identifiant utilisé est introduit dans la portée actuelle. (Si l'identifiant lui a été associé, il peut également être déclaré de nouveau avec le même type de liaison afin que les deux identificateurs se réfèrent au même objet ou à la même fonction)
De plus, le signe d'opérateur égal ( =
) peut être utilisé pour l'initialisation. Si une expression non évaluée (déclarateur) est suivie de =
dans la déclaration - on dit que l’identifiant introduit est également en cours d’initialisation. Après le signe =
on peut replacer une expression, mais cette fois-ci, elle sera évaluée et sa valeur sera utilisée comme initiale pour l'objet déclaré.
Exemples:
int l = 90; /* the same as: */
int l; l = 90; /* if it the declaration of l was in block scope */
int c = 2, b[c]; /* ok, equivalent to: */
int c = 2; int b[c];
Plus tard dans votre code, vous êtes autorisé à écrire exactement la même expression à partir de la partie déclaration de l'identifiant nouvellement introduit, en vous donnant un objet du type spécifié au début de la déclaration, en supposant que vous avez attribué des valeurs valides à tous objets dans le chemin. Exemples:
void f()
{
int b2; /* you should be able to write later in your code b2
which will directly refer to the integer object
that b2 identifies */
b2 = 2; /* assign a value to b2 */
printf("%d", b2); /*ok - should print 2*/
int *b3; /* you should be able to write later in your code *b3 */
b3 = &b2; /* assign valid pointer value to b3 */
printf("%d", *b3); /* ok - should print 2 */
int **b4; /* you should be able to write later in your code **b4 */
b4 = &b3;
printf("%d", **b4); /* ok - should print 2 */
void (*p)(); /* you should be able to write later in your code (*p)() */
p = &f; /* assign a valid pointer value */
(*p)(); /* ok - calls function f by retrieving the
pointer value inside p - p
and dereferencing it - *p
resulting in a function
which is then called - (*p)() -
it is not *p() because else first the () operator is
applied to p and then the resulting void object is
dereferenced which is not what we want here */
}
La déclaration de b3
spécifie que vous pouvez potentiellement utiliser la valeur b3
comme moyen d'accéder à un objet entier.
Bien sûr, pour appliquer l'indirection ( *
) à b3
, vous devez également y stocker une valeur correcte (voir les pointeurs pour plus d'informations). Vous devez également d'abord stocker une valeur dans un objet avant d'essayer de le récupérer (vous pouvez en savoir plus sur ce problème ici ). Nous avons fait tout cela dans les exemples ci-dessus.
int a3(); /* you should be able to call a3 */
Celui-ci indique au compilateur que vous allez essayer d'appeler a3
. Dans ce cas, a3
réfère à la fonction au lieu d'un objet. Une différence entre l’objet et la fonction est que les fonctions auront toujours une sorte de liaison. Exemples:
void f1()
{
{
int f2(); /* 1 refers to some function f2 */
}
{
int f2(); /* refers to the exact same function f2 as (1) */
}
}
Dans l'exemple ci-dessus, les deux déclarations se réfèrent à la même fonction f2
, alors que si elles déclaraient des objets, alors dans ce contexte (ayant deux portées de bloc différentes), elles seraient deux objets distincts différents.
int (*a3)(); /* you should be able to apply indirection to `a3` and then call it */
Maintenant, cela peut sembler compliqué, mais si vous connaissez les opérateurs de priorité, vous aurez 0 problème pour lire la déclaration ci-dessus. Les parenthèses sont nécessaires car l'opérateur *
a moins de priorité que celui ( )
.
Dans le cas de l'utilisation de l'opérateur subscript, l'expression résultante ne serait pas réellement valide après la déclaration car l'index utilisé (la valeur comprise entre [
et ]
) sera toujours 1 au-dessus de la valeur maximale autorisée pour cet objet / fonction.
int a4[5]; /* here a4 shouldn't be accessed using the index 5 later on */
Mais il devrait être accessible par tous les autres index inférieurs à 5. Exemples:
a4[0], a4[1]; a4[4];
a4[5]
donnera comme résultat UB. Vous trouverez plus d'informations sur les tableaux ici .
int (*a5)[5](); /* here a4 could be applied indirection
indexed up to (but not including) 5
and called */
Malheureusement pour nous, bien que syntaxiquement possible, la déclaration de a5
est interdite par la norme actuelle.
Typedef
Les typedefs sont des déclarations qui ont le mot-clé typedef
devant et avant le type. Par exemple:
typedef int (*(*t0)())[5];
( vous pouvez techniquement mettre le typedef après le type aussi - comme ceci int typedef (*(*t0)())[5];
mais ceci est déconseillé )
Les déclarations ci-dessus déclarent un identifiant pour un nom typedef. Vous pouvez l'utiliser comme ça après:
t0 pf;
Qui aura le même effet que l'écriture:
int (*(*pf)())[5];
Comme vous pouvez le voir, le nom typedef "enregistre" la déclaration en tant que type à utiliser ultérieurement pour d'autres déclarations. De cette façon, vous pouvez enregistrer certaines frappes. De même que la déclaration utilisant typedef
est toujours une déclaration, vous n'êtes pas limité uniquement par l'exemple ci-dessus:
t0 (*pf1);
Est le même que:
int (*(**pf1)())[5];
Utilisation de la règle de droite ou de spirale pour déchiffrer la déclaration C
La règle "right left" est une règle complètement régulière pour déchiffrer les déclarations C. Cela peut aussi être utile pour les créer.
Lisez les symboles lorsque vous les rencontrez dans la déclaration ...
* as "pointer to" - always on the left side
[] as "array of" - always on the right side
() as "function returning" - always on the right side
Comment appliquer la règle
ÉTAPE 1
Trouvez l'identifiant. C'est votre point de départ. Puis dites-vous, "l'identifiant est". Vous avez commencé votre déclaration.
ÉTAPE 2
Regardez les symboles à droite de l'identifiant. Si, par exemple, vous trouvez ()
, vous savez que c'est la déclaration d'une fonction. Vous auriez alors alors "l'identifiant est la fonction qui retourne" . Ou si vous avez trouvé un []
, vous diriez "identifiant est un tableau de" . Continuez tout droit jusqu'à ce que vous n'ayez plus de symboles OU appuyez sur une parenthèse droite )
. (Si vous appuyez sur une parenthèse gauche (
c'est le début d'un symbole ()
, même s'il y a des choses entre les parenthèses).
ÉTAPE 3
Regardez les symboles à gauche de l'identifiant. Si ce n'est pas l'un de nos symboles ci-dessus (par exemple, quelque chose comme "int"), dites-le simplement. Sinon, traduisez-le en anglais en utilisant ce tableau ci-dessus. Continuez à gauche jusqu'à épuisement des symboles OU appuyez sur une parenthèse gauche (
.
Répétez maintenant les étapes 2 et 3 jusqu'à ce que vous ayez formé votre déclaration.
Voici quelques exemples:
int *p[];
Tout d'abord, identifiant de recherche:
int *p[];
^
"p est"
Maintenant, déplacez-vous à droite jusqu'à ce que moins de symboles ou de parenthèses droites soient frappés.
int *p[];
^^
"p est un tableau de"
Ne peut plus bouger (sans symboles), déplacez-vous à gauche et trouvez:
int *p[];
^
"p est un tableau de pointeur vers"
Continuez à gauche et trouvez:
int *p[];
^^^
"p est un tableau de pointeur sur int".
(ou "p est un tableau dont chaque élément est de type pointeur sur int" )
Un autre exemple:
int *(*func())();
Trouvez l'identifiant.
int *(*func())();
^^^^
"func is"
Déplacer vers la droite.
int *(*func())();
^^
"func est la fonction qui retourne"
Ne peut plus bouger à cause de la parenthèse droite, alors déplacez-vous vers la gauche.
int *(*func())();
^
"func est la fonction qui retourne le pointeur sur"
Ne peut plus bouger à cause de la parenthèse gauche, alors continuez comme ça.
int *(*func())();
^^
"func est la fonction renvoyant le pointeur à la fonction retournant"
Je ne peux plus bouger parce que nous n'avons plus de symboles, alors allez à gauche.
int *(*func())();
^
"func est la fonction renvoyant le pointeur à la fonction retournant le pointeur à"
Et enfin, continuez à gauche, car il ne reste plus rien à droite.
int *(*func())();
^^^
"func est la fonction renvoyant le pointeur à la fonction renvoyant le pointeur à int".
Comme vous pouvez le voir, cette règle peut être très utile. Vous pouvez également l'utiliser pour vérifier vous-même pendant que vous créez des déclarations, et pour vous donner une idée de l'endroit où placer le symbole suivant et si des parenthèses sont nécessaires.
Certaines déclarations ont l'air beaucoup plus compliquées qu'elles ne le sont en raison de la taille des tableaux et des listes d'arguments sous forme de prototype. Si vous voyez [3]
, cela se lit comme "array (size 3) of ..." . Si vous voyez (char *,int)
qui se lit comme * "function attend (char , int) et retourne ..." .
Voici un amusant:
int (*(*fun_one)(char *,double))[9][20];
Je ne vais pas passer par chacune des étapes pour déchiffrer celle-ci.
* "fun_one est un pointeur sur la fonction qui attend (char , double) et retourne le pointeur sur le tableau (taille 9) du tableau (taille 20) de int."
Comme vous pouvez le voir, ce n'est pas si compliqué si vous vous débarrassez de la taille des tableaux et des listes d'arguments:
int (*(*fun_one)())[][];
Vous pouvez le déchiffrer de cette façon, puis placer les tailles de tableau et les listes d'arguments plus tard.
Quelques mots de conclusion:
Il est tout à fait possible de faire des déclarations illégales en utilisant cette règle, donc une connaissance de ce qui est légal dans C est nécessaire. Par exemple, si ce qui précède a été:
int *((*fun_one)())[][];
il aurait lu "fun_one est un pointeur sur la fonction retournant un tableau de tableau de pointeur sur int" . Une fonction ne pouvant pas retourner un tableau, mais uniquement un pointeur vers un tableau, cette déclaration est illégale.
Les combinaisons illégales comprennent:
[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array
Dans tous les cas ci-dessus, vous auriez besoin d'un ensemble de parenthèses pour lier un symbole *
à gauche entre ces symboles ()
et []
droite afin que la déclaration soit légale.
Voici quelques exemples supplémentaires:
Légal
int i; an int
int *p; an int pointer (ptr to an int)
int a[]; an array of ints
int f(); a function returning an int
int **pp; a pointer to an int pointer (ptr to a ptr to an int)
int (*pa)[]; a pointer to an array of ints
int (*pf)(); a pointer to a function returning an int
int *ap[]; an array of int pointers (array of ptrs to ints)
int aa[][]; an array of arrays of ints
int *fp(); a function returning an int pointer
int ***ppp; a pointer to a pointer to an int pointer
int (**ppa)[]; a pointer to a pointer to an array of ints
int (**ppf)(); a pointer to a pointer to a function returning an int
int *(*pap)[]; a pointer to an array of int pointers
int (*paa)[][]; a pointer to an array of arrays of ints
int *(*pfp)(); a pointer to a function returning an int pointer
int **app[]; an array of pointers to int pointers
int (*apa[])[]; an array of pointers to arrays of ints
int (*apf[])(); an array of pointers to functions returning an int
int *aap[][]; an array of arrays of int pointers
int aaa[][][]; an array of arrays of arrays of int
int **fpp(); a function returning a pointer to an int pointer
int (*fpa())[]; a function returning a pointer to an array of ints
int (*fpf())(); a function returning a pointer to a function returning an int
Illégal
int af[](); an array of functions returning an int
int fa()[]; a function returning an array of ints
int ff()(); a function returning a function returning an int
int (*pfa)()[]; a pointer to a function returning an array of ints
int aaf[][](); an array of arrays of functions returning an int
int (*paf)[](); a pointer to a an array of functions returning an int
int (*pff)()(); a pointer to a function returning a function returning an int
int *afp[](); an array of functions returning int pointers
int afa[]()[]; an array of functions returning an array of ints
int aff[]()(); an array of functions returning functions returning an int
int *fap()[]; a function returning an array of int pointers
int faa()[][]; a function returning an array of arrays of ints
int faf()[](); a function returning an array of functions returning an int
int *ffp()(); a function returning a function returning an int pointer
Source: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html