Aller au contenu

Exercices de programmation

Afin de compléter les compétences en C++ qui sont requises pour ce cours, des leçons sont mises à disposition sous forme de leçons EduTools. La marche à suivre pour l’installation des outils nécessaires et le démarrage de la leçon est faite pendant le cours.

Certains exercices seront revus en classe, mais il est attendu que les étudiants suivent ces leçons de façon autonome.

Points importants concernant la programmation orientée objet en C++

En comparaison au langage Java pratiqué au cours de la première année d’enseignement, il est intéressant de noter les points suivants :

  • Tout langage orienté objet a comme objectif de permettre de créer des objets ou instances de classe dont l’état est toujours cohérent. En particulier, l’état d’un objet doit être cohérent à sa création et tous les attributs de l’objet doivent être initialisés. C++, comme Java, réalise le chaînage des constructeurs afin d’assurer que les attributs hérités des classes parentes soient proprement initialisés. En C++, le chaînage des constructeurs par défaut est implicite et le chaînage des autres constructeurs est réalisé dans l’ initializer list.
  • En C++ comme en Java, un constructeur est appelé à la création d’un objet. Ce constructeur ne contient aucune instruction return.
  • Les constructeurs peuvent être surchargés, comme les méthodes. Un constructeur couramment surchargé est le copy constructor défini comme class(const class& other).

Contrairement à Java :

  • C++ ne garantit pas une initialisation par défaut des attributs d’une classe. Il faut noter que les dernières versions de C++ ont introduit des mécanismes d’initialisation par défaut, mais que ceux-ci sont restrictifs et que la bonne pratique veut donc que tous les attributs soient initialisés à la construction.
  • En C++, les opérateurs peuvent être surchargés. Il est donc par exemple possible de surcharger l’opérateur d’affectation qui est défini comme class& operator=(const class& other).

Explore memory layout

Exercice 1

Write a program that explores the memory layout of a C++ program.

  • Declare a uninitialized global variable and print its address.
  • Declare an initialized global variable and print its address.

1/ Observe the addresses of the two global variables and determine which one is located in the .bss section and which one is located in the .data section.

Note that you can use the readelf tool to inspect the memory layout of the compiled program and identify the sections where the variables are located.

  • Write a recursive function like below and call it from the main function.
  • Inside the function, declare a local variable and print its address.

2/ Where is this local variable located in memory? What happens to the address of this local variable when the function is called recursively? Can you explain why?

void recursiveFunction(int n) {
    int localVar[16]; // Declare a local variable (array of 16 integers)
    ...

    if (n > 0) {
        recursiveFunction(n - 1);
    }
}
  • Declare a static local variable inside the function and print its address.

3/ Where is this static local variable located in memory? What happens to the address of this static local variable when the function is called recursively? Can you explain why?

  • Add a dynamic allocation of an array of 16 integers inside the function and print its address.

4/ Where is this dynamically allocated array located in memory? What happens to the address of this dynamically allocated array when the function is called recursively? Can you explain why?

5/ What happen if you call the recursive function with a large value of n?

6/ How we configure the stack size of a C++ program? What are the implications of increasing or decreasing the stack size?