• 1. Vidéo

       

       

    • 2. Rappel codage décimal, hexadécimal et binaire

      Toute valeur entière peut être codée sous forme décimale (base 10), binaire (base 2) ou hexadécimale (base 16).

      Par exemple 2510=0001 10012=1916  (on a ajouté l’indice du nombre pour afficher la base). 

      Codage décimal
      Codage hexadécimal
      Codage binaire

      2*102  + 5*101 + 8*100

      2*162  + 5*161 + 8*160

      1*22  + 0*21 + 1*20

      Quizz
    • 3. Octets signés et non signés

      Nous avons vu jusqu'à présent un seul type de variable entière : le type int entier signé codé sur 32 bits ( sous Windows). Avant d'aller plus loin sur la numération binaire, voyons comment coder un nombre signé en binaire et pour cela prenons l'exemple le plus simple : l'octet.

      Un octet est constitué de 8 bits et permet de coder une valeur entière non signée (valeurs comprises entre 0 et 255). Mais comment coder un nombre signée avec un octet ? La solution : associer au bit de poids fort un poids négatif. Deux exemples associant valeur binaire et valeur décimale pour des octets non signés et signés sont donnés ci-dessous.

      Comme le bit de poids fort pour un octet signé est négatif, la valeur maximale sera 0111 11112 (12710) et la valeur minimale 1000 00002 (-12810)

      Comment définir un octet signé ou non signé en C ? Ce sont les types char et unsigned char qui permettent de définir un nombre entier signé ou non et codé sur 8 bits. La valeur d’une variable est par défaut en décimal sauf si l’on ajoute 0x devant, dans ce cas c’est de l’hexadécimal. En C, on ne peut pas utiliser 0b11111100 pour écrire la valeur en binaire, on ne peut utiliser que les valeurs en décimal 252 ou hexadécimal 0xFC.

      On peut aussi afficher la valeur décimale %d et hexadécimale %X d’un nombre entier signé ou non signé.

      Extrait de code
      Résultat affiché

      char c1 = 10;

      int n1 = 1023;

      printf("c1 = %d = %X\n", c1, c1);

      printf("n1 = %d = %X\n", n1, n1);

       

      c1 = 10 = A

      n1 = 1023 = 3FF

      Comment afficher un entier ou un octet non signé ? on utilisera %u (à la place de %d) pour afficher un octet non signé. On peut voir dans le deuxième exemple que 252 en décimal = 0xFC en hexadécimal.

      Extrait de code
      Résultat affiché

      unsigned char valeur = 9;

      printf("valeur=%u %X ", valeur, valeur);
      valeur=9 9 

      unsigned char valeur = 252;

      printf("valeur=%u %X ", valeur, valeur);

      valeur=252 FC 

      Comment afficher un entier ou un octet signé ? on utilisera %d (et %hhX pour l'affichage de l'octet en hexadecimal). Remarquez que la valeur -4 codée sur un octet signé a le même code hexadécimal que 252 en non signé !

      Extrait de code
      Résultat affiché

      char valeur = 9;

      printf("valeur=%d %X ", valeur, valeur);

      valeur=9 9 

      char valeur = -4;

      printf("valeur=%d %hhX ", valeur, valeur);

      valeur=-4 FC 

      Quizz

      Quelles sont les valeurs min et max d'un octet non signé ? chaque bit à un poids positif donc un octet non signé est compris entre 0 et 255.

      unsigned char valeur;

      Quelles sont les valeurs min et max d'un octet  signé ? le bit de poids fort a le poids négatif donc un octet signé est compris entre -128 et 127.

      char valeur;

    • 4. problème du dépassement de capacité (octet)

      Sachant que le type char (octet signé) est codé sur 8 bits, que se passe-t-il lorsqu'un calcul dépasse la capacité du nombre ? Pour répondre à la question voyons quel est le résultat de 127+1, le résultat de ce calcul pour une variable de type char est -128 (le bit de poids for a le poids négatif).

      Extrait de code
      Addition en mémoire
      Résultat

      char valeur_c=127;

      valeur_c += 1;

      printf("char : 127+1 = %d\n", valeur_c);

      char : 127+1 = -128

      ....

      Sachant que le type unsigned char (octet non signé) est codé sur 8 bits, que se passe-t-il lorsqu'un calcul dépasse la capacité du nombre ? Pour répondre à la question voyons quel est le résultat de 255+1, le résultat de ce calcul pour une variable de type unsigned char est 0 .

      Extrait de code
      Addition en mémoire
      Résultat

      unsigned char valeur_u = 255;

      valeur_u += 1;

      printf("unsigned char : 255+1 = %u\n", valeur_u );

      unsigned char : 255+1 = 0

      ...

    • 5. les différents types de variables (et leur codage sous Windows)

      Il existe plusieurs types de variables (8 bits, 16 bits, 32 bits) de type entier et 2 types de variables de type réel. Attention ce codage est vrai sous Windows et sera différent sous Linux.

      Type
             CODAGE BINAIRE
      CALCUL VALEUR
      MIN .. MAX

      char

      a7 .. a0

      - a727 + a626 +..+ a020

      -128 .. +127

      unsigned char

      a7 .. a0

       a727 + a626 +..+ a020

           0 .. +255

      short

      a15 .. a0

      - a15215 + a14214 +..+ a020

      -32768 à 32767

      unsigned short

      a15 .. a0

       a15215 + a14214 +..+ a020

      0 à 65535

      int

      long

      a31 .. a0

      - a31231 + a30230 +..+ a020

      - 2 Milliards .. +2 milliards

      unsigned int

      unsigned long

      a31 .. a0

       a31231 + a30230 +..+ a020

           0 .. +4 milliards

      float

      (s) (e7..e0) (m23.. m0)

      (s)Mantisse2exposant

       - ~1038 .. + ~1038

      double

      (s) (e9..e0) (m52.. m0)

      (s)Mantisse2exposant

      - ~10308 .. + ~10308

      Aller sur https://fr.wikipedia.org/wiki/Vol_501_d%27Ariane_5 et lire ce qui a provoqué l’explosion d’Ariane 5 pour son vol inaugural (vol 501).
      Quizz

    • 6. Portabilité des variables en C

      Les concepteurs du langage C, lors de sa création en 1972, n'ont pas imposé la taille des variables de type int et long. Avec le temps, des standards comme ANSI C (également connu sous le nom de C89 ou C90), et plus tard C99 (1999) et C11 (2011), ont apporté plus de précision sur les tailles minimales et les relations entre les types :

      • char : Doit être au moins de 8 bits.
      • short : Doit être au moins de 16 bits.
      • int : Doit être au moins de 16 bits et au moins aussi grand que short.
      • long : Doit être au moins de 32 bits et au moins aussi grand que int.
      • long long (introduit dans C99) : Doit être au moins de 64 bits et au moins aussi grand que long

      Le langage C est trés utilisé dans l'embarqué et notamment par les frameworks arduino et mbed. Arduino permet de programmer des microcontroleurs 8 bits (ATMEGA328P) mais aussi sur des microcontroleurs 32 bits. La taille d'un int différe entre un CPU 8 bits et 32 bits, ce qui correspond aux souhaits des concepteurs du langage. La taille du type int et long doit être associé aux capacités du microprocesseur utilisé.


      Remarque : un microcontroleur est constitué d'un microprocesseurs ou CPU (Central Processing Unit), de mémoire flash et RAM et de périphériques. Un microcontroleur permet d'exécuter des programmes sans composants extérieurs à la différence d'un microprocesseur qui nécessite des composants extérieurs pour fonctionner (RAM, FLASH, Horloge,...). La taille des bus de ces processeurs (8 bits, 16 bits, 32 bits ou 64 bits) associé à la vitesse du processeur permettent de donner une idée de leur puissance de calcul. Un processeur 8 bits possède une unité de calcul UAL (Arithmetic  and Logic Unit) capable d'exécuter des calculs sur des octets sur une ou plusieurs période d'horloge et donc si ce processeur doit réaliser des calculs sur 32 bits, il devra utiliser sont unité de calcul 8 bits plusieurs fois (ce sera donc bien plus long que pour un processeur 32 bits qui réalisera le calcul en une seule fois).


      Ce souhait des concepteurs d'avoir une taille des types int et long dépendant des machines peut être génant si l'on désire porter un programme écrit sur Windows vers Linux (ou Apple). En effet sous Windows le type long est codé sur 32 bits (quel que soit le type d'OS 32 ou 64 bits), ce qui n'est pas le cas sous Linux puisque le codage d'un long sur un OS 64 bits différe de celui sur un OS 32 bits.


      Sous Windows ou Linux c'est le type de système d'exploitation ou OS (Operating System) qui défini le type long. On a tendance à confondre type de CPU (32 ou 64 bits) et taille de l'OS (32 ou 64 bits) et c'est normal puisqu'il est préférable d'installer un OS 64 bits sur un CPU 64 bits, mais n'oublions pas cependant qu'il est possible d'installer un OS 32 bits sur un CPU 64 bits (mais non souhaitable).


    • 7. Les bibliothèques limits.h et stdint.h

      La bibliothèque des fonctions du C propose le fichier limits.h qui intègre les tailles min et max des différents types. Ce fichier sera donc différent sur ARDUINO, Windows et Linux et dépendra du type de processeur utilisé.

      #include <stdio.h>

      #include <limits.h>

      int main() {

       printf("The minimum value of CHAR = %d\n", CHAR_MIN);

       printf("The maximum value of CHAR = %d\n", CHAR_MAX);

       printf("The maximum value of UNSIGNED CHAR = %d\n", UCHAR_MAX);

       printf("The minimum value of SHORT INT = %d\n", SHRT_MIN);

       printf("The maximum value of SHORT INT = %d\n", SHRT_MAX);

       printf("The minimum value of INT = %d\n", INT_MIN);

       printf("The maximum value of INT = %d\n", INT_MAX);

       printf("The minimum value of LONG = %ld\n", LONG_MIN);

       printf("The maximum value of LONG = %ld\n", LONG_MAX);

      }

      Pour l'extrait de code ci-dessous, les résultats ne sont pas identiques puisque le codage d'un long et unsigned long sur Linux ou Windows diffère. Sous Linux les variables de long et unsigned long sont codées sur 64 bits et donc les valeurs maximum sont très grandes alors que sous Windows elles sont codées sur 32 bits. Le résultat des sommes dans les 2 cas amène à un dépassement de capacités puisque l'on ajoute 1 à la valeur maximale.

      unsigned long valeur_ul = ULONG_MAX;

      long valeur_l = LONG_MAX;

      valeur_ul += 1; // valeur_ul = valeur_ul + 1;

      valeur_l += 1; // valeur_l = valeur_l + 1;

      printf("unsigned long : %lu+1 = %lu\n", ULONG_MAX,valeur_ul);

      printf("long : %ld+1 = %ld\n", LONG_MAX,valeur_l);

      unsigned long : 18446744073709551615+1 = 0

      long : 9223372036854775807+1 = -9223372036854775808

      unsigned long : 4294967295+1 = 0

      long : 2147483647+1 = -2147483648

      Pour maitriser la taille des variables et ce quel que soit l'OS il existe une bibliothèque permettant de s'affranchir des différences de codage entre https://cours-moodle.fr/pix/img/chapitre8/7_3_linux.webpint et long : stdint.h. Ce fichier intègre des nouveaux types (grace au mot clé typedef) indépendant de la machine (ce fichier sera différent sur un OS Linux par rapport à un OS Windows).

      stdint.h

      typedef signed char        int8_t;

      typedef short              int16_t;

      typedef int                int32_t;

      typedef long long          int64_t;

      typedef unsigned char      uint8_t;

      typedef unsigned short     uint16_t;

      typedef unsigned int       uint32_t;

      typedef unsigned long long uint64_t;

      Voici un exemple utilisant la valeur maximale du type uint16_t définie dans stdint.h avec un test de dépassement de capacité.

      Programme de test utilisant stdint.h
      Résultat 

      #include <stdint.h>

      int main() {

          uint16_t valeur_16bits;

          valeur_16bits = UINT16_MAX;

          valeur_16bits++;

          printf("uint16_t : %u+1 = %u\n", UINT16_MAX, valeur_16bits);

          return 0;

      }

      uint16_t : 65535+1 = 0

       

      Quizz
    • 8 Les opérateurs logiques

      Tout microprocesseur possède une unité de calcule ALU (Arithmetic and Logic Unit). Nous n'avons vu jusqu'à présent que les calculs arithmétiques, passons aux opérateurs logiques qui sont à la base de tout circuit électronique. Tout d'abord un petit rappel sur les 4 opérations logiques de base.

      OU logique (OR)
      ET logique (AND)
      OU Exclusif (XOR)
      NON (NOT)

      Quizz

      Un microprocesseur 8 bits possède une ALU 8 bits et donc les opérations logiques se font sur 8 bits. En langage C, les opérateurs logiques permettent de travailler sur 8, 16, 32 ou 64 bits. Prenons un exemple simple utilisant le type 8 bits non signé et voyons le résultat des 4 opérations logiques sur 2 nombres : 143 et 138 (choix fait au hasard).

      opérations logiques  sur 8 bits
      Programme
      Résultat

       uint8_t n = 143;

       n = n & 138;

       printf("resultat : %u", n);

      resultat : 138

       uint8_t n = 143;

       n = n | 138;

       printf("resultat : %u", n);

       

      resultat : 143

       uint8_t n = 143;

       n = n ^ 138;

       printf("resultat : %u", n);

       

      resultat : 5

       uint8_t n = 143;

       n = ~ n;

       printf("resultat : %u", n);

       

      resultat : 112

      Quizz

      En plus des 4 opérations logiques, l'ALU du microprocesseurs propose les décalages de bits à droite ou à gauche. Les décalages à gauche correspondent à des multiplications par 2 alors que les décalages à droite des divisions par 2. Au niveau de l'ALU, un (ou plusieurs) décalages à droite ou à gauche sont plus rapides à effectuer que des multiplications ou divisions par 2.

      décalages de bits  sur 8 bits
      Programme
      Résultat

      int n=143;

      n <<= 1; //n = n << 1;

      printf("143<<1 = %d", n);

      143<<1 = 286

      int n=143;

      n <<= 2; //n = n << 2;

      printf("143<<2 = %d", n);

      143<<2 = 572

      int n=143;

      n <<= 1; //n = n << 1;

      printf("143<<1 = %d", n);

      143>>1 = 70

      int n=143;

      n <<= 2; //n = n << 2;

      printf("143<<2 = %d", n);

      143>>2 = 35

      Quizz

    • 9. Récapitulatifs des opérateurs logiques, arithmétiques et booléens

      Le tableau ci-dessous, résume toutes les opérations disponibles en langage C et affiche le résultat de ces opérations pour un octet signé et non signé. Attention à ne pas confondre les opérateurs de manipulation de bits (& | ^ et ~) qui travaillent sur des valeurs dont la taille dépend du type utilisé et les opérateurs booléens ou logique (&&, || et !) qui travaillent sur des variables booléennes (0 ou 1) et que l'on retrouve lors de tests de comparaisons.

      Classement
      Opérateurs
      SymboleS
      ExempleS

      RESULTATS

      unsigned char n;

      char n;

      En binaire

      Opérateurs

      arithmétiques

      Addition

      +

       n = 3 + 9 ;

      n = 12

      n = 12

        0000 1100

      Soustraction

      -

       n = 3 - 9 ;

      n = 250

      n = -6

       1111 1010

      Multiplication

      *

       n = 4 * 2 ;

      n = 8

      n = 8

       0000 1000

      Opérateurs

      de manipulation de bits

      Division

      /

       n = 5 / 2 ;

      n = 2

      n = 2

       0000 0010

      Modulo

      %

       n = 5 % 2 ;

      n = 1

      n = 1

       0000 0001

      ET

      &

       n = 143 & 138 ;

      n = 138

      n = -118

       1000 1010

      OU

      |

       n = 143 | 138 ;

      n = 143

      n = -113

       1000 1111

      OU exclusif

      ^

       n = 143 ^ 138 ;

      n = 5

      n = 5

       0000 0101

      Complément à 1

      ~

       n = ~4 ;

      n = 251

      n =  -5

       1111 1011

      Décalage à droite

      >> 

       n = 10 >> 2 ;

      n = 2

      n = 2

       0000 0010

      Décalage à gauche

      << 

       n = 10  <<2 ;

      n = 40

      n = 40

       0010 1000

      Opérateurs de comparaison

      Comparaison

      < <= > >=

       n=3<4;

      n=1

      n=1

       0000 0001

      Egal

      ==

       n=2==3;

      n=0

      n=0

       0000 0000

      Différent de

      !=

       n=2!=3;

      n=1

      n=1

       0000 0001

      Opérateurs

      booléens (ou logiques)

      ET

      &&

       n=(2<=3)&&(3<=5);

      n=1

      n=1

       0000 0001

      OU

      ||

       n=(2<=3)||(6<=5);

      n=1

      n=1

       0000 0001

      NON

      !

       n=!((2<=3)&&(3<=5));

      n=0

      n=0

       0000 0000

      Quizz
    • 10. Différences de codage entre float et double

      Il existe 2 solutions pour travailler sur des nombres réels en langage C:

      • en utilisant le type float (codage sur 32 bits)
      • en utilisant le type double (codage sur 64 bits).

      Afin de comprendre le principe du codage de ces nombres réels sur 32 bits on pourra aller sur https://www.h-schmidt.net/FloatConverter/IEEE754.html

      Le codage flottant permet de coder des trés petits nombres, comme de trés grands nombres. Il possède cependant un biais, certaines valeurs sont codées avec une erreur (liée au codage). C'est le cas de la valeur 0.1 par exemple qui va être codée par une valeur approchée 0.10000000149. Si l'on effectue un grand nombre d'additions de cette valeur, on peut alors voir apparaitre des erreurs de calcul. C'est l'objet du programme donné ci-dessous qui montre cette erreur de calcul pour le type float mais pas pour le type double qui étant codé sur 64 bits fournit une valeur plus précise.

      Extrait de programme
      Résultat

          int i;

          float fS = 0;

          for (i = 0; i < 100000; i++) // faire le calcul 100000 fois

              fS = fS + 0.1;

          printf("valeur apres calcul %g\n", fS);// résultat avec erreur

      valeur apres calcul 9998.56

          int i;

          double dS = 0;

          for (i = 0; i < 100000; i++) // faire le calcul 100000 fois

              dS = dS + 0.1;

          printf("valeur apres calcul %lg\n", dS);// résultat sans erreur

      valeur apres calcul 10000
    • 11. Cast implicite et cast explicite

      En C, le cast (conversion de type) permet de transformer une valeur d’un type vers un autre.
      Il existe deux types de conversions :

      • le cast implicite : c'est celui qui est fait automatiquement lorsqu'on calcule ou affecte une valeur

      • et le cast explicite , on force le changement de type

      Programme sans cast explicite
      Programme avec cast explicite

      int i, som = 0;

      double moyenne;

      for (i = 0; i < 4; i++) {

         som = som + i;

      }

      moyenne = som / i;

      printf("moyenne=%.2lf", moyenne);

      int i, som = 0;

      double moyenne;

      for (i = 0; i < 4; i++) {

         som = som + i;

      }

      moyenne = ((double)som) / (double)i;

      printf("moyenne=%.2lf", moyenne);

      moyenne=1.00

       

      moyenne=1.50

      Quizz
    • 12. Afficher en binaire un nombre

      Voici un exemple d'utilisation des opérateurs logiques permettant d'afficher en binaire un octet. Remarquez l'utilisation du masquage :

      valeur & (1<<i) qui permet de tester si le bit numéro i est à 1 ou à 0

      Programme 
      Résultat

      #include <stdio.h>

      #include <stdlib.h> 

      int main() {

        unsigned char n = 0xFC;

        printf("valeur=%u %X \n", n, n);

        char valeur = 0xFC;

        printf("valeur=%d %2X ", valeur, valeur);

        for (int i = 7; i >= 0; i--) {

          if (valeur & (1 << i))

            printf("1");

          else

            printf("0");

        }

        return 0;

      }

      valeur=252 FC

      valeur=-4 FC 11111100

      Quizz

    • 13. Complément sur la manipulation de bits

      La manipulation de bit sur des octets, mot de 16 32 ou 64 bits est très utile lorsqu'on travaille dans l'embarqué. Dans les frameworks Arduino ou mbed, la modification d'un bit d'un port de sortie noté D0,D1,D2,... utilise cette technique puisque les broches de sorties des microcontroleurs sont définies en PORT (PORTA, PORTB,...) constitués d'un ensemble de bits (8 bits pour des microcontroleurs 8 bits comme l'ATMEGA328P ou 16 bits pour les STM32). La modification d'un bit d'un PORT de sortie ne peut se faire qu'en utilisant le principe des masquages à 1 ou 0 avec les opérateurs logiques | ou &.

      Mettre le bit 3 à 1

      n=n|8;

      n = n | (1<<3) ;

      Mettre le bit 3 à 0

      n=n & ~8;

      n = n & ~(1<<3) ;

      Tester le bit 3

      if(n&8==(1<<3))
        printf("bit3 à 1");
      else
       printf("bit3 à 0");

      Quizz
    • 14. Test de fin de chapitre

    • Passons à la révision sur les conversion binaires et hexadécimales de variables entières, le cast, les types de variables entières signées ou non et les opérations logiques (incluant le masquage de bits).