Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
C
cours_prog
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
yassin.elhakoun
cours_prog
Commits
7ac6b537
Unverified
Commit
7ac6b537
authored
4 years ago
by
orestis.malaspin
Browse files
Options
Downloads
Patches
Plain Diff
modif old base 3
parent
b6e98fd5
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
base_3.md
+192
-357
192 additions, 357 deletions
base_3.md
with
192 additions
and
357 deletions
base_3.md
+
192
−
357
View file @
7ac6b537
% Base III
% Inspirés des slides de F. Glück
%
2 octo
bre 20
19
%
30 septem
bre 20
20
# Les tableaux (1/6)
## Généralités
-
`C`
offre uniquement des tableaux statiques
-
Un tableau est un "bloc" de mémoire contiguë associé à un nom
-
taille fixe déterminée à la déclaration du tableau
-
la taille ne peut pas être changée.
-
Pas d’assignation de tableaux.
-
Un tableau déclaré dans une fonction ou un bloc est détruit à la sortie de celle/celui-ci
-
$
\R
ightarrow$ Un tableau local à une fonction ne doit
**jamais être retourné**
(aussi valable pour toute variable locale)!
-
Les éléments d’un tableau sont accédés avec
`[i]`
{.C} où
`i`
{.C} est l’index de l’élément.
-
Le premier élément du tableau à l’index
`0`
{.C}!
-
Lorsqu’un tableau est déclaré, la taille de celui-ci doit toujours être spécifiée, sauf s’il est initialisé lors de sa déclaration.
# Les tableaux (2/6)
## Exemple
```
C
float tab1[5]; // tableau de floats à 5 éléments
// ses valeurs sont indéfinies
int tab2[] = {1, 2, 3}; // tableau de 3 entiers,
// taille inférée
int val = tab2[1]; // val vaut 2 à présent
int w = tab1[5]; // index hors des limites du tableau
// comportement indéfini!
// pas d'erreur du compilateur
```
<!-- TODO QUIZ:
```
C
int a1[5]; // OK
int a2[] = { 1, 2, 3 }; // OK
int a3[4][5]; // OK
int [] a4; // Erreur
int a5[]; // Erreur
int[] function(void) { // Erreur
int array[5]; // OK
return array; // Erreur
}
void foo(int a[]) { // OK
a[3] = 0; // OK
}
void bar(void) {
int a[5]; // OK
foo(a); // OK
a = a5; // Erreur
}
```
-->
<!--
```C
#include <stdio.h>
int main(void) {
char i;
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42;
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
```
-->
# Les tableaux (3/6)
## Itérer sur les éléments d'un tableau
```
C
int x[10];
for (int i = 0; i < 10; ++i) {
x[i] = 0;
}
int j = 0;
while (j < 10) {
x[j] = 1;
j += 1;
}
int j = 0;
do {
x[j] = -1;
j += 1;
} while (j < 9)
```
# Les tableaux (4/6)
## Les tableaux comme argument
-
Un tableau est le pointeur vers sa première case.
-
Pas moyen de connaître sa taille:
`sizeof()`
{.C} inutile.
-
Toujours spécifier la taille d'un tableau passé en argument.
```C
void foo(int tab[]) { // sans taille...
for (int i = 0; i < ?; ++i) {
// on sait pas quoi mettre pour ?
printf("tab[%d] = %d\n", i, tab[i]);
}
}
// n doit venir avant tab, [n] optionnel
void bar(int n, int tab[n]) {
for (int i = 0; i < n; ++i) {
printf("tab[%d] = %d\n", i, tab[i]);
}
}
```
# Les tableaux (5/6)
## Quels sont les bugs dans ce code?
```
C
#include <stdio.h>
int main(void) {
char i;
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42;
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
```
# Les tableaux (6/6)
## Quels sont les bugs dans ce code?
```
C
#include <stdio.h>
int main(void) {
char i;
// 200, .., 500 char overflow
char a1[] = { 100,200,300,400,500 };
char a2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
a2[10] = 42; // [10] out of bounds
for (i = 0; i < 5; i++) {
printf("a1[%d] = %d\n", i, a1[i]);
}
return 0;
}
```
<!-- TODO quiz: -->
<!-- que retourne sizeof(tab[]) -->
# Représentation des variables en mémoire (1/N)
## La mémoire
*
La mémoire est:
-
... un ensemble de bits,
-
... accessible via des adresses,
+------+----------+----------+------+----------+------+------+
| bits | 00110101 | 10010000 | .... | 00110011 | .... | .... |
+======+==========+==========+======+==========+======+======+
| addr | 2000 | 2001 | .... | 4000 | .... | .... |
+------+----------+----------+------+----------+------+------+
- ... gérée par le système d'exploitation.
- ... séparée en deux parties: **la pile** et **le tas**.
## Une variable
*
Une variable,
`type a = valeur`
{.C}, possède:
-
un type (
`char`
{.C},
`int`
{.C}, ...),
-
un contenu (une séquence de bits qui encode
`valeur`
{.C}),
-
une adresse mémoire (accessible via
`&a`
{.C}),
-
une portée.
# Représentation des variables en mémoire (2/N)

{#fig:memory width=100%}
# Les pointeurs (1/N)
-
Un pointeur est une adresse mémoire.
```C
type *id;
```
-
Pour interpréter le contenu de ce qu'il pointe, il doit être typé.
-
Un pointeur n'est rien d'autre qu'un entier (64bit sur x86-64, soit 8 octets).
-
Un pointeur peut être
**déréférencé**
: on accède à la valeur située à l'adresse mémoire sur laquelle il pointe.
```C
char *c; // déclaration pointeur de char
*c = 'a'; // assign. 'a' à valeur pointée par c
c = 1000; // on modifie l'adresse pointée par c
char d = *c; // on lit la valeur pointée par c. UB!
```
-
`NULL`
{.C} (ou
`0`
{.C}) est la seule adresse
**toujours**
invalide.
# Les pointeurs (2/N)

{#fig:memory width=100%}
# Les pointeurs (3/N)
-
Permettent d'accéder à une valeur avec une indirection.
```C
int a = 2;
int *b = &a;
*b = 7; // on met 7 dans la case pointée par b
// ici a == 7 aussi
a = -2; // ici *b == -2 aussi
```
-
Permettent d'avoir plusieurs chemins d'accès à une valeur.
-
Lire
**et**
écrire en même temps dans un bout de mémoire devient possible:
**danger**
.
# Quiz: Les pointeurs
## [Quiz: Les pointeurs](https://cyberlearn.hes-so.ch/mod/evoting/view.php?id=1038526)
# La fonction `sizeof()` (1/N)
-
La fonction
`sizeof()`
{.C} permet de connaître la taille en octets:
-
d'une valeur,
-
d'un type,
-
d'une variable.
-
Soit
`int a = 2`
{.C}, sur l'architecture x86_64 que vaut:
-
`sizeof(a)`
{.C}?
-
`sizeof(&a)`
{.C}?
-
Soit
`char b = 2`
{.C},
-
`sizeof(b)`
{.C}?
-
`sizeof(&b)`
{.C}?
# La fonction `sizeof()` (2/N)
-
Réponses:
-
`sizeof(a) == 4`
{.C},
`int`
{.C} entier 32 bits.
-
`sizeof(&a) == 8`
{.C}, une adresse est de 64 bits.
-
`sizeof(b) == 1`
{.C},
`char`
{.C} entier 8 bits.
-
`sizeof(&b) == 8`
{.C}, une adresse est de 64 bits.
# Types complexes: `struct`{.C} (1/N)
# Types complexes: `struct`{.C} (1/5)
## Généralités
...
...
@@ -283,7 +23,7 @@ int main(void) {
num.im = -2.0;
```
# Types complexes: `struct`{.C} (2/
N
)
# Types complexes: `struct`{.C} (2/
5
)
## Simplifications
...
...
@@ -305,7 +45,7 @@ int main(void) {
complex_t num2 = num; // copie
```
# Types complexes: `struct`{.C} (3/
N
)
# Types complexes: `struct`{.C} (3/
5
)
## Pointeurs
...
...
@@ -320,116 +60,93 @@ int main(void) {

{#fig:compilation width=100%}
# Types complexes: `struct`{.C} (4/5)
# Allocation dynamique de mémoire (1/N)
-
La fonction
`malloc`
{.C} permet d'allouer dynamiquement (pendant l'exécution du programme) une zone de mémoire contiguë.
```C
#include <stdlib.h>
void *malloc(size_t size);
```
-
`size`
{.C} est la taille de la zone mémoire
**en octets**
.
-
Retourne un pointeur sur la zone mémoire ou
`NULL`
{.C} en cas d'échec:
**toujours vérifier**
que la valeur retournée est
`!= NULL`
{.C}.
# Allocation dynamique de mémoire (2/N)
## Initialisation
-
Avec l'exemple de tout à l'heure:
-
Avec le passage par
**référence**
on peut modifier un struct
*en place*
.
-
Les champs sont accessible avec le sélecteur
`->`
{.C}
```C
complex_t *num = malloc(sizeof(complex_t));
num->re = 1.0; // maintenant ...
num->im = -1.0; // ça marche.
void complex_init(complex_t *num,
double re, double im)
{
// num a déjà été allouée
num->re = re;
num->im = im;
}
int main() {
complex_t num; // on alloue un complexe
complex_init(&num, 2.0, -1.0); // on l'initialise
}
```
-
La zone mémoire
**n'est pas**
initialisée.
-
La mémoire doit être désallouée explicitement $
\R
ightarrow$
**fuites mémoires**
.
<!-- - Toujours garder un pointeur sur la mémoire allouée sinon **pointeur pendouillant**. -->

{#fig:compilation width=100%}
# Types complexes: `struct`{.C} (5/5)
#
Allocation dynamique de mémoire (3/N)
#
#
Initialisation version copie
-
La fonction
`free()`
{.C} permet de libérer une zone préalablement allouée avec
`malloc()`
{.C}.
*
On peut allouer un complexe, l'initialiser et le retourner.
*
La valeur retournée peut être copiée dans une nouvelle structure.
```C
#include <stdlib.h>
void free(void *ptr);
complex_t complex_create(double re, double im) {
complex_t num;
num.re = re;
num.im = im;
return num;
}
int main() {
// on crée un complexe et on l'initialise
// en copiant le complexe créé par complex_create
// deux allocation et une copie
complex_t num = complex_create(2.0, -1.0);
}
```
-
Pour chaque
`malloc()`
{.C} doit correspondre exactement un
`free()`
{.C}.
-
Si la mémoire n'est pas libérée:
**fuite mémoire**
(l'ordinateur plante quand il y a plus de mémoire).
-
Si la mémoire est
**libérée deux**
fois: seg fault.
-
Pour éviter les mauvaises surprises mettre
`ptr`
{.C} à
`NULL`
{.C}.
#
Allocation dynamique de mémoire (4/N
)
#
Les fonctions (1/2
)
##
Tableaux dynamiques
##
Arguments de fonctions par copie
-
Pour allouer un espace mémoire de 50 entiers:
-
Les arguments d'une fonction sont toujours passés
**par copie**
.
-
Les arguments d'une fonction ne peuvent
**jamais**
être modifiés.
```C
int *p = malloc(50 * sizeof(int));
```
-
Cette espace peut alors être utilisé comme un tableau de 50 entiers:
```C
for (int i = 0; i < 50; ++i) {
p[i] = 0;
void do_something(complex_t a) { // a: nouvelle variable
// valeur de a est une copie de x
// lorsque la fonction est appelée, ici -1
a.re += 2.0;
a.im -= 2.0;
} // a est détruite
int main() {
complex_t x;
do_something(x); // x est passé en argument
// x sera inchangé
}
```
## Arithmétique de pointeurs
-
On peut parcourir la mémoire différemment qu'avec l'indexation
```
C
int *p = malloc(50 * sizeof(int));
// initialize somehow
double a = p[7];
double b = *(p + 7); // on avance de 7 "double"
p[0] == *p; // le pointeur est le premier élément
```
# Allocation dynamique de mémoire (5/N)
*
Que pourrait-on faire pour modifier
`x`
{.C}?
#
#
Arithmétique de pointeurs
#
Les fonctions (2/2)

{#fig:compilation width=100%}
## Arguments de fonctions par référence
# Allocation dynamique de mémoire (6/N)
## Pointeur de pointeur
-
Tout comme une valeur a une adresse, un pointeur a lui-même une adresse:
-
Pour modifier un variable, il faut passer son
**adresse mémoire**
en argument.
-
L'adresse d'une variable,
`a`
{.C}, est accédée par
`&a`
{.C}.
-
Un
**pointeur**
vers une variable entière
`a`
le type,
`int *`
{.C}.
-
La syntaxe
`*a`
{.C} sert à
**déréférencer**
le pointeur (à accéder à la mémoire pointée).
```C
int a = 2;
int *b = &a;
int **c = &b;
```
-
Chaque
`*`
{.C} ou
`&`
{.C} rajoute une indirection.
# Allocation dynamique de mémoire (7/N)
## Pointeur de pointeur

{#fig:compilation height=100%}
void do_something(complex_t *a) { // a: un nouveau pointeur
// valeur de a est une copie de d'un pointeur
// les données pointées sont les données originales
# Allocation dynamique de mémoire (8/N)
-
Avec
`malloc()`
, on peut allouer dynamiquement des tableaux de pointeurs:
```C
int **p = malloc(50 * sizeof(int*));
for (int i = 0; i < 50; ++i) {
p[i] = malloc(70 * sizeof(int));
}
int a = p[5][8]; // on indexe dans chaque dimension
a->re += 2.0;
a->im -= 2.0;
} // le pointeur a est détruit, *a est toujours là et a été modifié
```
-
Ceci est une matrice (un tableau de tableau).
# Prototypes de fonctions (1/N)
# Prototypes de fonctions (1/3)
## Principes généraux de programmation
...
...
@@ -447,8 +164,7 @@ int main(void) {
-
Libraire
`stdio.h`
:
`printf()`
{.C},
`scanf()`
{.C}, ...
# Prototypes de fonctions (2/N)
# Prototypes de fonctions (2/3)
-
Prototypes de fonctions nécessaires quand:
1. Utilisation de fonctions dans des fichiers séparés.
...
...
@@ -457,7 +173,7 @@ int main(void) {
-
On met les prototypes des fonctions
**publiques**
dans des fichiers
*headers*
, extension
`.h`
.
-
Les
*implémentations*
des fonctions vont dans des fichier
`.c`
.
# Prototypes de fonctions (3/
N
)
# Prototypes de fonctions (3/
3
)
## Fichier header
...
...
@@ -475,13 +191,13 @@ int main(void) {
#include "chemin/du/prototypes.h"// chemin explicite
```
# Génération d'un exécutable (1/
N
)
# Génération d'un exécutable (1/
5
)
## Un seul fichier source

{#fig:compilation width=100%}
# Génération d'un exécutable (2/
N
)
# Génération d'un exécutable (2/
5
)
## Un seul fichier source
...
...
@@ -496,13 +212,13 @@ gcc proc.c -o prog
Les différents codes intermédiaires sont effacés.
# Génération d'un exécutable (3/
N
)
# Génération d'un exécutable (3/
5
)
## Plusieurs fichiers sources

{#fig:compilation_plusieurs width=100%}
# Génération d'un exécutable (4/
N
)
# Génération d'un exécutable (4/
5
)
::: Main
...
...
@@ -553,7 +269,7 @@ int sum(int tab[], int n) {
::::::::::::::
# Génération d'un exécutable (
4/N
)
# Génération d'un exécutable (
5/5
)
La compilation séparée se fait en plusieurs étapes.
...
...
@@ -576,7 +292,7 @@ La compilation séparée se fait en plusieurs étapes.
$ gcc main.o sum.o -o prog
```
# Préprocesseur (1/
N
)
# Préprocesseur (1/
2
)
## Généralités
...
...
@@ -601,7 +317,7 @@ La compilation séparée se fait en plusieurs étapes.
#define NOM_MACRO(arg1, arg2, ...) [code]
```
# Préprocesseur (2/
N
)
# Préprocesseur (2/
2
)
## La directive `include`{.C}
...
...
@@ -624,5 +340,124 @@ La compilation séparée se fait en plusieurs étapes.
#endif
```
# Les tableaux (1/2)
## Généralités
-
`C`
offre uniquement des tableaux statiques
-
Un tableau est un "bloc" de mémoire contiguë associé à un nom
-
taille fixe déterminée à la déclaration du tableau
-
la taille ne peut pas être changée.
-
Pas d’assignation de tableaux.
-
Un tableau déclaré dans une fonction ou un bloc est détruit à la sortie de celle/celui-ci
-
$
\R
ightarrow$ Un tableau local à une fonction ne doit
**jamais être retourné**
(aussi valable pour toute variable locale)!
-
Les éléments d’un tableau sont accédés avec
`[i]`
{.C} où
`i`
{.C} est l’index de l’élément.
-
Le premier élément du tableau à l’index
`0`
{.C}!
-
Lorsqu’un tableau est déclaré, la taille de celui-ci doit toujours être spécifiée, sauf s’il est initialisé lors de sa déclaration.
# Les tableaux (2/2)
## Exemple
```
C
float tab1[5]; // tableau de floats à 5 éléments
// ses valeurs sont indéfinies
int tab2[] = {1, 2, 3}; // tableau de 3 entiers,
// taille inférée
int val = tab2[1]; // val vaut 2 à présent
int w = tab1[5]; // index hors des limites du tableau
// comportement indéfini!
// pas d'erreur du compilateur
```
# La ligne de commande (1/4)
## Point d'entrée d'un programme
-
Le point d'entrée est la fonction
`main()`
{.C}.
-
Elle peut être déclarée de 4 façon différentes:
1.
`void main()`
{.C}.
2.
`int main()`
{.C}.
3.
`void main(int argc, char **argv)`
{.C}.
4.
`int main(int argc, char **argv)`
{.C}.
-
`argc`
{.C} est le nombre d'arguments passés à la ligne de commande:
**le premier est celui du programme lui-même**
.
-
`argv`
{.C} est un tableau de chaînes de caractères passés sur la ligne de commande.
# La ligne de commande (2/4)
## Exemple d'utilisation
Pour la fonction dans le programme
`prog`
```
C
int main(int argc, char **argv)
```
Pour l'exécution suivante on a
```
bash
$
./prog
-b
50 file.txt
```
```
C
argc == 4
argv[0] == "prog"
argv[1] == "-b"
argv[2] == "50"
argv[3] == "file.txt"
```
# La ligne de commande (3/4)
## Conversion des arguments
-
Les arguments sont toujours stockés comme des
**chaînes de caractère**
.
-
Peu pratique si on veut manipuler des valeurs numériques.
-
Fonctions pour faire des conversions:
```C
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
double atof(const char *nptr);
int snprintf(char *str, size_t size,
const char *format, ...);
// str: buffer, size: taille en octets max à copier,
// format: cf printf(), ret: nombre de char lus
```
# La ligne de commande (4/4)
## Exemple d'utilisation
\f
ootnotesize
```
C
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
int main(int argc, char **argv) {
if (argc != 3) {
char *progname = basename(argv[0]);
fprintf(stderr, "usage: %s name age\n", progname);
return EXIT_FAILURE;
}
char *name = argv[1];
int age = atoi(argv[2]);
printf("Hello %s, you are %d years old.\n", name, age);
return EXIT_SUCCESS;
}
```
```
bash
$
./prog Paul 29
Hello Paul, you are 29 years old.
```
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment