Skip to content
Snippets Groups Projects
base_1.md 16.51 KiB

% Programmation séquentielle en C % Base I - Inspirés des slides de F. Glück % 18 septembre 2019

Historique (1/2)

  • Conçu initialement pour la programmation des systèmes d’exploitation (UNIX).
  • Créé par Dennis Ritchie à Bell Labs en 1972 dans la continuation de CPL, BCPL et B.
  • Standardisé entre 1983 et 1988 (ANSI C).
  • La syntaxe de C est devenue la base d’autres langages comme C++, Objective-C, Java, Go, C#, Rust, etc.
  • Révisions plus récentes, notamment C99, C11, puis C18.

Historique (2/2)

  • Développement de C lié au développement d’UNIX.
  • UNIX a été initialement développé en assembleur:
    • Les instructions assembleur sont de très bas niveau
    • Les instructions assembleur sont spécifiques à l’architecture du processeur.
  • Pour rendre UNIX portable, un langage de haut niveau (en 1972) était nécessaire.
  • Comparé à l’assembleur, le C est :
    • Un langage de "haut niveau": C offre des fonctions, des structures de données, des constructions de contrôle de flots (while{.C}, for{.C}, etc).
    • Portable: un programme C peut être exécuté sur un très grand nombre de plateformes (il suffit de recompiler le même code pour l’architecture voulue).

Qu'est-ce que le C?

  • "Petit langage simple" (en 2019).
  • Langage compilé, statiquement (et faiblement) typé, procédural, portable, très efficace.
  • Langage "bas niveau" (en 2019): management explicite et manuelle de la mémoire (allocation/désallocation), grande liberté pour sa manipulation.
  • Pas de structures de haut niveau: chaînes de caractères, vecteurs dynamiques, listes, ...
  • Aucune validation ou presque sur la mémoire (pointeurs, overflows, ...).

Exemple de programme

#include <stdio.h>
#include <stdlib.h>
int main() {
  printf("Enter n: "); // affichage
  int n = 0; // déclaration et initialisation de n
  scanf("%d", &n); // entrée au clavier
  int sum = 0; // déclaration et initialisation de sum
  for (int i = 0; i <= n; ++i) { // boucle
    sum += i;
  }
  printf("Sum of the %d first integers: %d\n", n, sum);
  if (sum != n * (n+1) / 2) { // branchement cond.
    printf("Error: the answer is wrong.\n");
    return EXIT_FAILURE; // code d'erreur
  }
  return EXIT_SUCCESS; // code de réussite
}

Génération d'un exécutable

  • Pour pouvoir être exécuté un code C doit être d'abord compilé (avec gcc ou clang).

  • Pour un code prog.c la compilation "minimale" est

    $ clang prog.c
    $ ./a.out # exécutable par défaut
  • Il existe une multitude d'options de compilation:

    1. -std=c11 utilisation de C11.
    2. -Wall et -Wextra activation des warnings.
    3. -fsanitize=… contrôles d’erreurs extensifs à l’exécution (au prix d’un coût en performance). Sur Ubuntu 14.04 -fsanitize=leak et -fsanitize=undefined ne sont pas supportés (cat /etc/lsb-release indique la version).
    4. -g symboles de débogages sont gardés.
    5. -o défini le fichier exécutable à produire en sortie.
$ clang -std=c11 -Wall -Wextra -g porg.c -o prog 
  -fsanitize=address -fsanitize=leak -fsanitize=undefined

La simplicité de C?

  • 32 mots-clé et c'est tout

auto{.C} double{.C} int{.C} struct{.C}
break{.C} else{.C} long{.C} switch{.C}
case{.C} enum{.C} register{.C} typedef{.C}
char{.C} extern{.C} return{.C} union{.C}
const{.C} float{.C} short{.C} unsigned{.C} continue{.C} for{.C} signed{.C} void{.C} default{.C} goto{.C} sizeof{.C} volatile{.C} do{.C} if{.C} static{.C} while{.C}


Déclaration et typage

En C lorsqu'on veut utiliser une variable (ou une constante), on doit déclarer son type

const double two = 2.0; // déclaration et init.
int x;   // déclaration (instruction)
char c;  // déclaration (instruction)
x = 1;   // affectation (expression)
c = 'a'; // affectation (expression)
int y = x; // déclaration et initialisation en même temps
int a, b, c; // déclarations multiples
int d = e = f = 1; // décl./init. multiples

Types de base (1/4)

Numériques

Type Signification (gcc pour x86-64)


char{.C}, unsigned char{.C} Entier signé/non-signé 8-bit short{.C}, unsigned short{.C} Entier signé/non-signé 16-bit int{.C}, unsigned int{.C} Entier signé/non-signé 32-bit long{.C}, unsigned long{.C} Entier signé/non-signé 64-bit float{.C} Nombre à virgule flottante, simple précision double{.C} Nombre à virgule flottante, double précision


La signification de short{.C}, int{.C}, ... dépend du compilateur et de l'architecture.

Types de base (2/4)

Voir <stdint.h> pour des représentations portables

Type Signification


int8_t{.C}, uint8_t{.C} Entier signé/non-signé 8-bit int16_t{.C}, uint16_t{.C} Entier signé/non-signé 16-bit int32_t{.C}, uint32_t{.C} Entier signé/non-signé 32-bit int64_t{.C}, uint64_t{.C} Entier signé/non-signé 64-bit


Types de base (3/4)

Booléens

  • Le ANSI C n'offre pas de booléens.
  • L'entier 0{.C} signifie faux, tout le reste vrai.
  • Depuis C99, la librairie stdbool met à disposition un type bool{.C}.
  • En réalité c'est un entier:
    • 1 \Rightarrow
      true{.C}
    • 0 \Rightarrow
      false{.C}
  • On peut les manipuler comme des entier (les sommer, les multiplier, ...).

Types de base (4/4)

Conversions

  • Les conversions se font de manière:
    • Explicite:
      int a = (int)2.8;
      double b = (double)a;
      int c = (int)(2.8+0.5);
    • Implicite:
      int a = 2.8; // warning, si activés, avec clang
      double b = a + 0.5;
      char c = b; // pas de warning...
      int d = 'c';

Expressions et opérateurs (1/6)

Une expression est tout bout de code qui est évalué.

Expressions simples

  • Pas d'opérateurs impliqués.
  • Les littéraux, les variables, et les constantes.
const int L = -1; // 'L' est une constante, -1 un littéral
int x = 0;        // '0' est un litéral
int y = x;        // 'x' est une variable
int z = L;        // 'L' est une constante

Expressions complexes

  • Obtenues en combinant des opérandes avec des opérateurs
int x;     // pas une expression (une instruction)
x = 4 + 5; // 4 + 5 est une expression
           // dont le résultat est affecté à 'x'

Expressions et opérateurs (2/6)

Opérateurs relationnels

Opérateurs testant la relation entre deux expressions:

  • (a opérateur b) retourne 1{.C} si l'expression s'évalue à true{.C}, 0{.C} si l'expression s'évalue à false{.C}.
Opérateur Syntaxe Résultat
<{.C} a < b{.C} 1 si a < b; 0 sinon
>{.C} a > b{.C} 1 si a > b; 0 sinon
<={.C} a <= b{.C} 1 si a <= b; 0 sinon
>={.C} a >= b{.C} 1 si a >= b; 0 sinon
=={.C} a == b{.C} 1 si a == b; 0 sinon
!={.C} a != b{.C} 1 si a != b; 0 sinon

Expressions et opérateurs (3/6)

Opérateurs logiques

Opérateur Syntaxe Signification
&&{.C} a && b{.C} ET logique
` `{.C}
!{.C} !a{.C} NON logique

Expressions et opérateurs (4/6)

Opérateurs arithmétiques

Opérateur Syntaxe Signification
+{.C} a + b{.C} Addition
-{.C} a - b{.C} Soustraction
*{.C} a * b{.C} Multiplication
/{.C} a / b{.C} Division
%{.C} a % b{.C} Modulo

Expressions et opérateurs (5/6)

Opérateurs d'assignation

Opérateur Syntaxe Signification
={.C} a = b{.C} Affecte la valeur b à la variable a
et retourne la valeur de b
+={.C} a += b{.C} Additionne la valeur de b à a et
assigne le résultat à a.
-={.C} a -= b{.C} Soustrait la valeur de b à a et
assigne le résultat à a.
*={.C} a *= b{.C} Multiplie la valeur de b à a et
assigne le résultat à a.
/={.C} a /= b{.C} Divise la valeur de b à a et
assigne le résultat à a.
%={.C} a %= b{.C} Calcule le modulo la valeur de b à a et
assigne le résultat à a.

Expressions et opérateurs (6/6)

Opérateurs d'assignation (suite)

Opérateur Syntaxe Signification
++{.C} a++{.C} Incrémente la valeur de a de 1 et
retourne le résultat (a += 1).
--{.C} a--{.C} Décrémente la valeur de a de 1 et
retourne le résultat (a -= 1).
++{.C} ++a{.C} Retourne a{.C} et incrémente a de 1.
--{.C} --a{.C} Retourne a{.C} et décrémente a de 1.

Structures de contrôle: if{.C} .. else if{.C} .. else{.C} (1/2)

Syntaxe

if (expression) {
    instructions;
} else if (expression) { // optionnel
                         // il peut y en avoir plusieurs
    instructions;
} else {
    instructions; // optionnel
}
if (x) { // si x s'évalue à `vrai`
    printf("x s'évalue à vrai.\n");
} else if (y == 8) { // si y vaut 8
    printf("y vaut 8.\n");
} else {
    printf("Ni l'un ni l'autre.\n");
}

Structures de contrôle: if{.C} .. else if{.C} .. else{.C} (2/2)

Pièges

int x = y = 3;
if (x = 2)
    printf("x = 2 est vrai.\n");
else if (y < 8)
    printf("y < 8.\n");
else if (y == 3)
    printf("y vaut 3 mais cela ne sera jamais affiché.\n");
else
    printf("Ni l'un ni l'autre.\n");
    x = -1; // toujours évalué

Structures de contrôle: switch{.C} .. case{.C} (1/2)

switch (expression) {
    case constant-expression:
        instructions;
        break; // optionnel
    case constant-expression:
        instructions;
        break; // optionnel
    // ...
    default:
        instructions;
}

Que se passe-t-il si break{C} est absent?

Structures de contrôle: switch{.C} .. case{.C} (2/2)

int x = 0;
switch (x) {
    case 0:
    case 1:
        printf("0 ou 1\n");
        break;
    case 2:
        printf("2\n");
        break;
    default:
        printf("autre\n");
}

Dangereux, mais c'est un moyen d'avoir un "ou" logique dans un case.

Structures de contrôle: for{.C}, while{.C}, do ... while{.C} (1/4)

La boucle for{.C}

for (expression1; expression2; expression3) {
    instructions;
}

La boucle while{.C}

while (expression) {
    instructions;
}

La boucle do ... while{.C}

do {
    instructions;
} while (expression);

Structures de contrôle: for{.C}, while{.C}, do ... while{.C} (2/4)

La boucle for{.C}

int sum = 0; // syntaxe C99
for (int i = 0; i < 10; i++) {
    sum += i;
}

for (int i = 0; i != 1; i = rand() % 4) { // ésotérique
    printf("C'est plus ésotérique.\n");
}

Structures de contrôle: for{.C}, while{.C}, do ... while{.C} (3/4)

La boucle while{.C}

int sum = 0, i = 0;
while (i < 10) { // pas assuré·e·s de faire un tour
    sum += i;
    i += 1;
}

La boucle do ... while{.C}

int sum = 0, i = 0;
do { // assuré·e·s de faire un tour
    sum += i;
    i += 1;
} while (i < 10);

Structures de contrôle: continue{.C}, break{.C} (4/4)

  • continue{.C} saute à la prochaine itération d'une boucle.

    int i = 0;
    while (i < 10) {
        if (i == 3) {
            continue;
        }
        printf("%d\n", i);
        i += 1;
    }
  • break{.C} quitte le bloc itératif courant d'une boucle.

    for (int i = 0; i < 10; i++) {
        if (i == 3) {
            break;
        }
        printf("%d\n", i);
    }

Les variables (1/2)

Variables et portée

  • Une variable est un identifiant, qui peut être liée à une valeur (un expression).
  • Une variable a une portée qui définit où elle est visible (où elle peut être accédée).
  • La portée est globale ou locale.
  • Une variable est globale est accessible à tout endroit d'un programme et doit être déclarée en dehors de toute fonction.
  • Une variable est locale lorsqu'elle est déclarée dans un bloc, {...}{.C}.
  • Une variable est dans la portée après avoir été déclarée.

Les variables (2/2)

Exemple

int bar() { // x, y pas visibles ici, max oui }

int foo() {
    int x = 1; // x est locale à foo
    {
        // x est visible ici, y pas encore
        int y = 2;
        bar(); // ni x ni y sont visible dans bar()
    } // y est détruite à la sortie du bloc
} // x est à la sortie de foo

float max; // variable globale accessible partout

int main() {
    int z; // locale, à main
} // z est détruite ici, max aussi

Entrées/sorties: printf(){.C} (1/2)

Généralités

  • La fonction printf(){.C} permet d'afficher du texte sur le terminal:

    int printf(const char *format, ...);
  • Nombre d'arguments variables.

  • format{.C} est le texte, ainsi que le format (type) des variables à afficher.

  • Les arguments suivants sont les expressions à afficher.

Entrées/sorties: printf(){.C} (2/2)

Exemple

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("Hello world.\n");
    int val = 1;
    printf("Hello world %d time.\n", val);
    printf("%f squared is equal to %f.\n", 2.5, 2.5*2.5);
    return EXIT_SUCCESS;
}

Entrées/sorties: scanf(){.C} (1/2)

Généralités

  • La fonction scanf(){.C} permet de lire du texte formaté entré au clavier:

    int scanf(const char *format, ...);
  • format{.C} est le format des variables à lire (comme printf(){.C}).

  • Les arguments suivants sont les variables où sont stockées les valeurs lues.

Entrées/sorties: scanf(){.C} (2/2)

Exemple

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("Enter 3 numbers: \n");
    int i, j, k;
    scanf("%d %d %d", &i, &j, &k);
    printf("You entered: %d %d %d\n", i, j, k);
    
    return EXIT_SUCCESS;
}