Memory fault.Core dumped »,
« Segmentation fault.Core dumped »
ou « Bus error.Core dumped ».
▶ Un pointeur (ce peut être une variable ou une constante) correspond à l'adresse d'un emplacement en mémoire. Une telle
adresse peut être celle d'une variable ou d'un espace ne correspondant
pas directement à une variable : il peut s'agir de la valeur d'une expression de pointeur ou de l'adresse d'un espace alloué dynamiquement
suite à une demande d'allocation explicite.
▶ Alors qu'en Java, le concept de pointeur apparaît de fait de manière implicite avec le concept de référence sur objet
d'une classe définie (à opposer aux variables de types primitifs qui s'apparentent aux variables du langage C), le langage C permet
des manipulations explicites
(mis à part éventuellement pour les tableaux où elle est cachée si on les utilise par indexation classique).
Bidule qu'il a lui-même définie, il obtenait,
(après avoir créé un objet et affecté le résultat de cette opération à
la variable) sans rien demander de particulier (un simple System.out.print), une information du genre :
Bidule@192d342
x quelconque sur un type T, l'opérateur qui permet
d'accéder à l'adresse de l'espace associé à cette variable est l'opérateur d'adressage ou de référencement qui est noté
« & ».
print est %p, format dédié à l'affichage des adresses sous forme hexadécimale
--> cat adresses_variables.c
#include <stdio.h>
#include <stdlib.h>
int a; // une variable globale a de type int
/* définition d'une structure et d'une variable initialisée */
struct{int a, b;} st = {3, 4};
int main( ) {
int b = 10;
static int c = 20;
printf("adresse de a : %p\n", &a);
printf("adresse de b : %p\n", &b);
printf("adresse de c : %p\n", &c);
printf("adresse de st : %p\n", &st);
printf("adresse de st.a : %p\n", &st.a);
printf("adresse de st.b : %p\n", &st.b);
return EXIT_SUCCESS; // return 0;
}
--> ./adresses_variables
adresse de a : 0x203c
<-- adresse de la variable a (zone statique)
adresse de b : 0xbffff8dc
<-- adresse de la variable b (sur la pile)
adresse de c : 0x201c
<-- adresse de la variable c (zone statique)
adresse de st : 0x2014
<-- adresse de la variable st (zone statique)
adresse de st.a : 0x2014
<-- adresse de st.a = adresse de st
adresse de st.b : 0x2018
<-- adresse de st.b
-->
--> cat adresse_tableau.c
#include <stdio.h>
#include <stdlib.h>
int tab[4] = {1,2,3,4};
int main( ) {
printf("%p\n", tab);
printf("%p\n", &tab[0]);
printf("%p\n", &tab);
return EXIT_SUCCESS; // return 0;
}
--> ./adresse_tableau
0x2014
0x2014
0x2014
-->
tab,
&tab[0]
et &tab
sont des pointeurs vers la même adresse en mémoire.
Cependant en examinant les choses d'un peu plus près on peut observer des diifférences entre ces trois valeurs du point de vue de leur type et taille
(et par suite, en anticipant sur ce que nous dirons plus loin, sur
leur arithmétique, par exemple par la valeur obtenue en ajoutant 1).
| expression | valeur
(format %p) |
type | taille
( sizeof *) |
valeur + 1 |
|---|---|---|---|---|
tab |
0x600940 |
pointeur sur int |
4 | 0x600944 |
&tab[0] |
0x600940 |
pointeur sur int |
4 | 0x600944 |
&tab |
0x600940 |
pointeur sur tableau de 16 int |
16 | 0x600950 |
* »
▶ Ainsi, la définition d'une variable p du type ««pointeur sur T »,
ou de manière abrégée « pointeur sur T » correspond à l'instruction
*p;
De fait, cette écriture ne fait qu'exprimer que la variable p pointe sur une
zone mémoire dont la valeur *p est de type T.
▶ Le type « void * joue un rôle particulier : il
définit un type de pointeur générique. Ce type de pointeur est compatible avec
tous les autres types de pointeurs : un pointeur de ce type peut pointer vers n'importe quel type.
Il s'agit d'un type à part entière (et non pas d'un pointeur vers quelque chose de type void, ce qui n'existe pas).
Ce type joue un rôle central dans le prototypage des fonctions pour désigner :
malloc).
--> cat type_pointeur.c
#include <stdio.h>
#include <stdlib.h>
void *p_void;
int *p_int;
struct couple{int a, b;} *p_couple;
typedef struct couple *pointeur_couple;
int main() {
pointeur_couple ptr;
printf("taille de p_void : %ld\n", sizeof(p_void));
printf("adresse de p_void : %p\n", &p_void);
printf("valeur de p_void : %p\n", p_void);
printf("taille de ptr : %ld\n", sizeof(ptr));
printf("adresse de ptr : %p\n", &ptr);
printf("valeur de ptr : %p\n", ptr);
return EXIT_SUCCESS; // return 0;
}
--> ./type_pointeur
taille de p_void : 4
adresse de p_void : 0x2038
valeur de p_void : 0x0
taille de ptr : 4
adresse de ptr : 0xbffff8dc
valeur de ptr : 0x1000
-->
&p_void » est du type « pointeur sur pointeur sur void »
correspondant au type void **;0x0.
ptr dans l'exécution ci-dessus).
char *p[] ». La variable p est du type « tableau de pointeurs sur char ». En effet dans l'interprétation de ce qu'est « *p[i] », l'indexation est prioritaire : ainsi « p[i] » est évalué en premier et est de type « char * »;int (*p)(int, int) » . La variable p est du type « pointeur sur fonction ayant deux paramètres de type int renvoyant un int.
p, on obtient
une expression de la forme « (*p)(a,b) » et pour l'évaluer :
p : p est donc un pointeurp est donc un pointeur sur une fonction ayant deux paramètres de type int;int.
char *(*p(void))[] ». Si on considère une utilisation de p, on obtient
une expression « (*p())[j] »
qui est de type char.
*. Cela signifie donc que p est une fonction*. cela signifie que la fonction renvoie un pointeurcharp
avec le type « fonction retournant un pointeur sur un tableau de pointeurs sur char ».
**
donne la valeur de la grandeur de type T qu'elle pointe.
Autrement dit, la valeur de l'expression « *ptr »
est la valeur de type T pointée par ptr.
ptr
est adressable
(c'est-à-dire dans le cas
où ptr est une variable), le déréférencement est l'opération
inverse de la prise d'adresse (référencement) et les deux
expressions « ptr » et
expressions « *&ptr »
sont alors équivalentes.
p_st
pointant sur une structure (ou une union), « *p_st » désigne le contenu en mémoire.
▶ Si la structure associée possède un champ
de nom champ, l'accès à ce champ
pour la zone pointée par p_st est réalisé avec l'expression
« (*p_st).champ ». Les parenthèses sont nécessaires en raison des priorités respectives
des opérateurs *
et ..
▶ Afin de faciliter l'écriture de tels accès, un opérateur spécifique a été introduit .
->.
p_st->st » permet l'accès au champ champ
de la structure pointée par p_st.
--> cat ptr3.c
#include <stdio.h>
#include <stdlib.h>
int main ( ) {
#include <stdio.h>
 
int i = 10;
 
char c[6] = {'A', 'E', 'I', 'O', 'U', 'Y'};
 
int *p_int = &i;
 
printf("%d\n", *p_int);
 
char *p_char = c; // équivalent à p_char = &c[0]
 
printf("%c %c %c\n", *p_char, *p_char + 1, *(p_char + 1));
 
return EXIT_SUCCESS; // return 0;
}
--> ./ptr3
10
A B E
-->
(T *) m ».
(void *) m ».
Memory fault.Core dumped »), soit ce qui est sans doute plus grave
car non signalée, une écriture en un endroit indésirable de la mémoire, ce qui risque de conduire à des résultats erronés.
NULL est prédéfinie dans le fichier
d'interface stdio.h. Elle est associée à la valeur « (void *)0x0 » (pointeur nul).
▶ Etant donnée une variable x de type, T,
&x est une constante du type « pointeur sur T ».
▶ Un tableau t défini de type T constitue une constante du type pointeur sur « T ».
Cela signifie que l'identificateur t n'est pas acceptable en partie gauche d'une affectation.
▶ Un identificateur de fonction définie est un pointeur constant sur fonction.
static)
est initialisée à la valeur null,
à moins d'une demande explicite dans la définition;static
ou non initialisées explicitement) ne font l'objet d'aucune initialisation : le contenu de l'espace qui leur est alloué au moment où cette
allocation est réalisée en constitue la valeur (la suite de 0 et de 1
est interprétée comme une adresse).=.
Le membre droit droit d'unne telle affectation peut être :
++)
et pré et post-d\écrémentation (--)+) ou soustraction d'un entier (-)+= et -=)==,
!=, <, etc.). char » dont la valeur vue comme un entier est n
donnera à ce pointeur la valeur vue comme un entier de n+1short » dont la valeur vue comme un entier est n donnera à ce pointeur la valeur vue comme un entier de n+2 (puisque la taille d'un short est 2)t est un identificateur de
tableau dont les éléments sont de type T et n est un
entier, l'expression « t+n » constitue de fait une constante de pointeur
sur l'élément t[n] et n'est en fait rien d'autre que
&t[n].
fonc de prototype
fonc(T1,... ,Tn);
(*)(T1,... ,Tn);
ptr_fonc vers une fonction telle que la fonction
fonc précédente est réalisée par :
(*ptr_fonc)(T1,... ,Tn);
typedef T (*pointeur_fonction)(T1,... ,Tn);
pointeur_fonction ptr_fonc;
--> cat ptrFonc.c
#include <stdio.h>
#include <stdlib.h>
/* définition du type pointeur sur fonction ayant
2 paramètres de type int et renvoyant un int */
typedef int (*pointeur_fonction)(int, int);
int somme(int a, int b){ return a + b;}
int produit(int a, int b){ return a * b;}
/* déclaration d'une fonction renvoyant un entier
et ayant en paramètres un pointeur sur fonction
renvoyant un int et ayant 2 int en paramètres */
int calculer(int (*)(int, int), int, int);
/* définition d'une fonction ayant en paramètre un
caractère et renvoyant un pointeur sur fonction
renvoyant un int et ayant 2 int en paramètres */
int (*quelle_fonction(char c))(int, int) {
if (c == '+') return somme;
else return produit;
}
pointeur_fonction tab_fonc[2] = {somme, produit};
int main() {
pointeur_fonction ptr_f;
int (*ptr_fonc)(int, int);
ptr_fonc = somme;
printf("%d\n", (*ptr_fonc)(4,5));
ptr_f = produit;
printf("%d\n", (*ptr_f)(4,5));
printf("%d\n", calculer(somme, 6, 7));
printf("%d\n", calculer(produit, 6, 7));
printf("%d\n", (*tab_fonc[0])(100,45));
printf("%d\n", ((*quelle_fonction)('*'))(32,6));
return EXIT_SUCCESS; // return 0;
}
/* définition de la fonction calculer déclarée plus haut */
int calculer(int (*f)(int, int), int a, int b){
return (*f)(a,b);
}
--> ./ptrFonc
9 <--- on a calculé somme(4,5)
20 <--- on a calculé produit(4,5)
13 <--- on a calculé somme(6,7)
42 <--- on a calculé produit(6,7)
145 <--- on a calculé somme(100,45)
192 <--- on a calculé produit(32,6)
-->