dimanche 19 avril 2020

C++ : Passage d'arguments par valeur, par référence et par pointeur

Le langage C++ offre des performances incroyables à condition bien sûr de bien comprendre ses subtilités. La façon de passer des arguments lors de l'appel d'une fonction ou d'une méthode peut faire une grande différence. Il en va de même pour le retour des valeurs des méthodes. On peut soit passer les arguments par valeur, par référence ou par pointeur.


Passage par valeur


Lorsqu'on passe un argument par valeur, on se trouve à fournir une copie de l'élément à la fonction ou la méthode.
Exemple :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;

void printLine(string line);

int main(int argc, char* argv[])
{
    string my_line = "Line original";
    cout << "main function:\t\t" << my_line << endl;
    printLine(my_line);
    cout << "main function:\t\t" << my_line << endl;
    return 0;
}

void printLine(string line)
{
    line = "Line updated";
    cout << "printLine function:\t" << line << endl;
}

Trace de l'exécution

jed@jed-Ubuntu:~/Programming/test$ g++ main.cpp && ./a.out
main function:          Line original
printLine function:     Line updated
main function:          Line original

On peut voir ici que la variable my_line est passée en argument à la fonction printLine mais malgré que la variable line est modifiée à l'intérieur de la fonction, cela n'a aucun effet sur la variable my_line, car à la sortie de la fonction la méthode l'affiche à l'écran et elle a sa valeur originale.

Ce mode de passage devrait être préféré pour les types fondamentaux du C++ tel qu'énuméré ici : https://www.learncpp.com/cpp-tutorial/73-passing-arguments-by-reference/

La plupart de ces types ont une empreinte mémoire plus petite qu'un pointeur (4 octets sur les systèmes x86 et 8 octets pour les systèmes x64) alors il est préférable de les passer par valeur.


Passage par référence (symbole &)

Le passage par référence n'utilise pas de ressource supplémentaire comparativement au passage par valeur qui doit créer une copie de la variable à chaque appel. Dans le cas d'une fonction récursive ou dans le cas où l'argument aurait une empreinte mémoire assez imposante, ça peut vite affecter la performance de l'exécution. Dans le but d'optimiser les ressources utilisées, il est dans la plupart des cas préférable d'utiliser le passage par référence.

Exemple :
#include <iostream>

using namespace std;

void printLine(string &line);

int main(int argc, char* argv[])
{
    string my_line = "Line original";
    cout << "main function:\t\t" << my_line << endl;
    printLine(my_line);
    cout << "main function:\t\t" << my_line << endl;
    return 0;
}

void printLine(string &line)
{
    line = "Line updated";
    cout << "printLine function:\t" << line << endl;
}

Trace de l'exécution

jed@jed-Ubuntu:~/Programming/test$ g++ main.cpp && ./a.out
main function:          Line original
printLine function:     Line updated
main function:          Line updated

On peut voir dans cet exemple que la variable my_line est passée en argument à la fonction printLine et que la modification qui est apportée à l'argument line à l'intérieur de la fonction a aussi eu pour effet de modifier la variable my_line. On le voit bien à la sortie de la fonction lorsqu'on affiche à l'écran le contenu de la variable my_line et qu'elle a la valeur "Line updated". L'argument line était donc une référence qui pointait vers l'espace mémoire de la variable my_line.



Passage par référence constante

Heureusement on peut obtenir le meilleur des deux mondes en passant la variable par référence constante. De cette façon, aucune ressource supplémentaire n'est utilisée, on ne pourra pas modifier l'argument et on s'assure de n'avoir aucun effet de bord.

Si on reprend l'exemple précédent voici la syntaxe pour le passage en référence constante:

void printLine(const string &line);

Si on roule le code ci-dessous, on peut voir que la modification de la valeur de l'argument line est interdite.

void printLine(const string &line)
{
    line = "Line updated";
    cout << "printLine function:\t" << line << endl;
}

Trace de l'exécution

jed@jed-Ubuntu:~/Programming/test$ g++ main.cpp && ./a.out
main.cpp: In function ‘void printLine(const string&)’:
main.cpp:18:12: error: passing ‘const string {aka const std::__cxx11::basic_string<char>}’ as ‘this’ argument discards qualifiers [-fpermissive]
     line = "Line updated";
            ^~~~~~~~~~~~~~

Le mode de passage par référence (constant ou non) devrait être utilisé pour tous les types personnalisés (classes et struct) qui auront toujours une valeur (non nulle).



Passage par pointeur

Il est toujours possible d'utiliser la bonne vieille méthode de passage par pointeur mais celle-ci est un peu plus complexe. Nous en reparlerons plus loin. Voyons pour l'instant comment utiliser notre exemple vu plus haut, mais en passage par pointeur.

Exemple :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;

void printLine(string *line); 

int main(int argc, char * argv[]) 
{
    string my_line = "Line original";
    cout << "main function:\t\t" << my_line << endl;
    printLine(&my_line);
    cout << "main function:\t\t" << my_line << endl;
    return 0;
}

void printLine(string *line)
{
    *line = "Line updated";
    cout << "printLine function:\t" << *line << endl;
}

Trace de l'exécution

jed@jed-MS-7593:~/Programming/test$ g++ src/main.cpp && ./a.out
main function:          Line original
printLine function:     Line updated
main function:          Line updated

On obtient exactement le même résultat que le passage par référence. Aucune copie de valeur n'a été effectuée, en fait ce que nous avons passé en argument c'est une copie du pointeur de la variable my_line qui continent l'adresse mémoire d'où la valeur "Line original" est stockée.

Remarquer à la ligne 18 que l'on doit utiliser l'opérateur de déréférencement * pour assigner "Line updated" dans l'espace mémoire pointé par le pointeur. Même principe à la ligne 19 pour récupérer les données de l'adresse mémoire en question.

Voici ce qui survient si l'on n'utilise pas l'opérateur de déréférencement * à la ligne 19 :

jed@jed-MS-7593:~/Programming/test$ g++ src/main.cpp && ./a.out
main function:          Line original
printLine function:     0x7ffc1d2856d0
main function:          Line updated

C'est l'adresse mémoire contenue par le pointeur qui est affiché au lieu de la valeur de ce qui est contenu à cette adresse.



Passage par pointeur constant

Il y a deux façons de faire:

  • En utilisant la notation const string * line on s'assure que le contenu du pointeur ne pourra pas être changé.
  • En utilisant la notation string * const line on s'assure que l'adresse du pointeur ne pourra pas être changée.
1
2
3
4
5
void printLine(const string * line)
{
    *line = "Line updated"; //Erreur on ne peut pas changer la valeur contenue par le pointeur
    cout << "printLine function:\t" << *line << endl;
}

1
2
3
4
5
6
void printLine(string * const line)
{
    string lineUpdated = "Line updated";
    line = &lineUpdated; //Erreur on ne peut pas changer l'adresse du pointeur
    cout << "printLine function:\t" << *line << endl;
}

On peut aussi utiliser la combinaison des deux méthodes afin d'avoir la totale : const string * const line.

1
2
3
4
5
6
7
void printLine(const string * const line)
{
    string lineUpdated = "Line updated";
    line = &lineUpdated; //Erreur on ne peut pas changer l'adresse du pointeur
    *line = lineUpdated; //Erreur on ne peut pas changer la valeur contenue par le pointeur
    cout << "printLine function:\t" << *line << endl;
}

Le mode de passage par pointeur (constant ou non) devrait être utilisé pour tous les types personnalisés (classes et struct) lorsqu'ils peuvent être nul. Si par exemple on veut passer une instance d'une classe que nous allons appeler Employé mais qu'il est possible que l'instance soit nulle alors on doit utiliser un passage par pointeur. Ce mode peut aussi être très pratique lorsqu'on veut modifier le pointeur rȩcu pour le faire pointer sur un autre objet.



Conclusion

Tout au long du développement d'une application on peut avoir à travailler avec les 3 modes, il est donc important de connaître l'utilité de chacun des modes et quand on devrait les utiliser.

dimanche 23 avril 2017

Détection de memory leaks avec Microsoft Visual Studio 2015 (C++)

Cette semaine je cherchais un moyen de vérifier si le projet sur lequel je travail contient des "memory leaks".

Après une petite recherche, je suis tombé sur la page "CRT Debugging Techniques" du site MSDN (https://msdn.microsoft.com/en-us/library/zh712wwf.aspx).
La page contient une section qui se nomme "Finding Memory Leaks using the CRT Library".

Qu'est que la CRT Library? En français, la bibliothèque runtime C (CRT) est la partie de la bibliothèque C++ standard qui incorpore la bibliothèque ISO C99 standard. Cette bibliothèque contient entres autres plusieurs macros/fonctions permettant d'aider au débogage de code natif. Il suffit d'inclure dans votre code quelques macros et l'appel d'une fonction pour vérifier à la fin de l'exécution si votre code contient des memory leaks.

Prenons un code assez simple en exemple :
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>
#include <string>

using namespace std;

int main()
{
    string* chaineTresLongue = new string("blablabla...");
 
    string* cloneChaine = new string(*chaineTresLongue);
    delete cloneChaine;

    return 0;
}


Lorsqu'on exécute de code en mode debug l'application se termine normalement. Aucune fuite de mémoire à l'horizon.
Sortie de la fenêtre Output module Debug
Il y a pourtant une fuite de mémoire car nous n'avons jamais supprimer le pointeur de chaîne de caractères chaineTresLongue. Bon évidemment dans un petit projet comme celui là nous aurions pu détecter la fuite de mémoire rapidement à l’œil mais dans un gros projet qui contient des milliers de lignes de code, c'est beaucoup plus difficile.

Pour activer la détection il suffit d'inclure le fichier d'entête crtdbg.h et d'appeler la fonction _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); au début de la fonction principale du programme.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
#include <string>
#include <crtdbg.h>

using namespace std;

int main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    string* chaineTresLongue = new string("blablabla...");
    string* cloneChaine = new string(*chaineTresLongue);

    delete cloneChaine;
    return 0;
}

On voit maintenant qu'un memory leak a bel et bien été détecté.
Sortie de la fenêtre Output module Debug
Petit détail par contre, il serait pratique de connaitre le numéro de la ligne d'où l'espace mémoire non libéré a été créé pour la première fois. Pour ce faire, il suffit d'ajouter quelques macros sur le fichier source principal.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <string>
#include <crtdbg.h>
#ifdef _DEBUG
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define new DEBUG_NEW
#endif

using namespace std;

int main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    string* chaineTresLongue = new string("blablabla...");
    string* cloneChaine = new string(*chaineTresLongue);

    delete cloneChaine;
    return 0;
}

Voilà! Un autre outil aider à créer du code natif sans fuite de mémoire.

Sortie de la fenêtre Output module Debug





mardi 3 janvier 2017

Nouveaux projets C++

Voilà un bon bout de temps que je n'ai pas bloggé mais le développement a quand même continuer durant tout ce temps.

J'ai développé un logiciel pour le département d'ingénierie de la mine Canadian Malartic qui permet de gérer la planification du forage et du sautage.




J'ai aussi créé MineComm, un logiciel de diagnostic et de performance pour les réseau mesh de Rajant. Lien vers MineComm



J'ai ensuite créé mon site web jedsharpsoftware.com.



Tout ces derniers projets étaient en C# mais depuis quelques mois je me suis relancé dans le langage de programmation que je préfère par dessus tout, le C++.

J'ai donc créé deux petites librairies, histoire de me remettre dans le bain.

La première s'agit d'une librairie de gestion de dates à la .NET en C++ et la deuxième est un client SMTP pour l'envoi de courriel.

Vous trouverez les 2 librairies sur mon GitHub. Elles sont encore en développement donc libre à vous de collaborer pour les faire évoluer. :)

https://github.com/jeremydumais

mardi 2 juillet 2013

TypeScript

Il y a quelques temps que j'avais entendu parler de ce nouveau langage qui se nomme TypeScript. TypeScript est un langage typé, contrairement au Javascript, qui à la compilation, génère du code Javascript. Il met à la disposition des développeurs web les concepts de la programmation orienté objet. À cette époque, qui n'est pas si lointaine soit dit en passant, je développais principalement en ASP.net et j'avais peu recours au Javascript donc je ne m'y était pas trop attardé.

Un peu plus tard, j'ai dû développer un module sur un Extranet. Le back-end étant un serveur PHP, et moi qui est un néophyte en ce qui concerne le PHP, j'ai donc décidé de coder le maximum du module en javascript et de laisser seulement les opérations BD et la sécurité au serveur PHP. Ce fût un projet assez complexe, j'ai donc dû créer d'énormes fichiers Javascript avec multitudes de variables, prototype, fonctions etc. Bref au final, le résultat fût beaucoup de code éparpillé et dur à maintenir.

TypeScript met donc revenu en tête. Ce langage est conçu pour développer des applications de grande envergure qui permet de regrouper le code sous forme de classes, namespaces, interfaces etc. Offrant les concepts de l'orienté objet, il supporte bien entendu l'héritage. La dernière version 0.9 offre même le support des Generic.

La courbe d'apprentissage

Une des ses forces à mon avis est que la courbe d'apprentissage peut-être vraiment être graduelle. En fait, on peut créer un fichier TypeScript et écrire du code 100% Javascript et ça fonctionne. On peut donc utiliser du code Javascript d'un projet existant et typer certaines variables ou recoder certaines classes. Il est possible de profiter des forces qu'offre TypeScript sans avoir à tout ré-écrire.

Les librairies incontournables

L'utilisation des librairies tel que JQuery, Knockout, LINQ etc. fonctionnent en TypeScript à condition de référencer les fichiers de définitions de la librairie. Ces fichiers décrivent les objets de la librairie, ce qui permet à TypeScript de valider que l'appel à ces objets est bien valide. Les fichiers de définitions des différentes librairies peuvent être trouvé sur Definitely Types ou via NuGet.

Conclusion

Évidemment tout ce qu'on peut faire avec TypeScript, on peut également le faire en javascript car comme je le disait plus haut, la compilation d'un fichier TypeScript (.ts) créer un fichier Javascript. Son but est d'offrir au développeur une syntaxe beaucoup plus simple et le langage typé permet de détecter des erreurs à la compilation au lieu de les découvrir au runtime. Je ne pourrai plus m'en passer en ce qui me concerne! ;)

http://www.typescriptlang.org/

dimanche 18 novembre 2012

NDateTime (DateTime du .NET porté pour JavaScript)

Voilà ma dernière création, une librairie JavaScript qui regroupe les fonctionnalités de base d'un objet DateTime en .NET

Plus besoin de se battre avec les dates JavaScript! :)

Pour tous les développeurs web.
Si vous avez des idées (Ajouts/Commentaires) n'hésitez pas!



http://ndatetime.codeplex.com

dimanche 19 février 2012

Comment ouvrir une nouvelle fenêtre en javascript à la suite d'un postback (ASP.NET)

Il peut paraître assez simple à première vue d'ouvrir une fenêtre en javascript à la suite d'un postback. On aurait tendance à ce dire qu'il suffit d'enregistrer un script avec la commande window.open. Le problème c'est que les navigateurs sont maintenant munis de "Pop-up blocker". Les navigateurs empêchent donc toutes ouvertures de fenêtre en javascript sauf si l'action provient d'un clic sur un lien ou un bouton.

Heureusement, il y a une façon de contourner ce problème. La solution est d'ouvrir la fenêtre sur l'évènement onclick côté client et de lui assigner un nom unique. Ensuite, lorsque vous atteignez l'évènement buttonclick côté serveur, il suffit d'utiliser la commande window.open avec le même nom spécifié côté client. Le navigateur va donc utiliser la même fenêtre ouverte précédemment côté client. De cette façon, on respecte les règles d'ouvertures de fenêtre en javascript des navigateurs.

Voici un exemple très simple, je vous l'accorde, mais qui démontre la solution : Voici le bouton du bouton dans notre fichier aspx
<asp:Button ID="ButtonOpenGoogle" Text="Cliquer pour ouvrir Google" /> OnClick="ButtonOpenGoogle_Click" /> OnClientClick="window.open('about:blank', 'windowPopupGoogle');" />
Voici le code de l'évènement click du bouton côté serveur
Dans l'exemple ci-dessus la fenêtre est identifiée de façon unique avec le nom 'windowPopupGoogle'.

Il peut arriver aussi qu'une fois côté serveur, on veule annuler l'ouverture de la fenêtre, il suffit donc de récupérer le handler de la fenêtre et d'effectuer une window.close telle que dans l'exemple ci-dessous.
 protected void ButtonOpenGoogle_Click(object sender, EventArgs e)
{
ScriptManager.RegisterStartupScript(this, typeof(Page), "windowPopupGoogleScript", "var win = window.open('about:blank','windowPopupGoogle');win.close();", true);
}
Voilà!

mardi 24 janvier 2012

Comment neutraliser un spambot qui utilise un formulaire web

Un spambot est un logiciel qui permet d'envoyer des pourriels. Il existe plusieurs types de spambots. Certains recherchent des adresses courriel en parcourant des pages web pour ainsi se créer une liste d'adresses et ensuite d'envoyer des pourriels à cette liste. Il en existe également pour les forums, les wikis donc pour les formulaires web en général.

Solution Captcha

Il existe une technologie qui se nomme Captcha (Completely Automated Public Turing test to tell Computers and Humans Apart) qui permet d'effectuer une vérification en utilisant la capacité d'analyse d'image ou de son de l'être humain. Les Captcha visuels fournissent des mécanismes d'altération de l'image (déformation légère, points aléatoires, ligne transversale, etc.) afin que les logiciels de reconnaissance optique de caractères ne puissent pas décoder les caractères sur l'image. Malheureusement, les spambots les plus perfectionnés sont en mesure de contourner les Captcha. Certains Captcha sont tellement déformés pour éviter une reconnaissance automatique que même les internautes ne peuvent les reconnaître.
Exemple de Captcha


Solution Honeypot (Pot de miel)

Il existe une façon que je trouve assez intéressante pour régler ce problème. Il s'agit en gros de tendre un piège aux spambots afin de pouvoir les neutraliser. Les spambots ne sont pas capables de distinguer à quoi servent les champs de votre formulaire. Ils sont capables de détecter les types de champs, mais non leurs fonctions. Les spambots ont l'habitude de remplir de données bidon tous les champs du formulaire web (afin de contourner les RequiredValidator) alors grosso modo le truc est d'ajouter un champ bidon (caché en CSS) afin que l'intru y ajoute des données. Ce champ n'étant pas visible aux utilisateurs "normaux", nous pourrons donc déduire que le formulaire a été saisi par un robot si le champ contient des données et par un utilisateur si le champ est vide.

Dans le l'événement OnInit de la page de formulaire, nous allons ajouter le champ texte bidon et lui assigner une classe CSS afin de pouvoir le cacher. La classe CSS aura un nom généré au hasard au format suivant :
trap + nombre aléatoire de 1 à 1000
Le nom de la classe est généré afin que le spambot ne puisse pas détecter l'astuce.
protected override void OnInit(EventArgs e)
{
base.OnInit(e); //Add a HoneyPot to block spambots //Generate a random class name Random random = new Random();
String honeyPotCssClassName = String.Concat("trap", random.Next(1000).ToString());
TextBox textHoneyPot = new TextBox()
{
ID = "textHoneyPot",
CssClass = honeyPotCssClassName
};
Form.Controls.Add(textHoneyPot);

ClientScript.RegisterClientScriptBlock(typeof(Page), "_dynamiccsstrap", String.Concat("<style type=\"text/css\">.",honeyPotCssClassName," { display:none !important; } </style>"), false);
}
Lors que le formulaire est retourné au serveur, on doit s'assurer que le champ textHoneyPot est vide avant de poursuivre le traitement.
 protected void ButtonSend_Click(object sender, EventArgs e)
{
TextBox textHoneyPot = (TextBox)Form.FindControl("textHoneyPot");
if (Page.IsValid && String.IsNullOrEmpty(textHoneyPot.Text))
{
//Do stuff
}
}

Voilà, le problème des spambots devrait être réglé, du moins jusqu'à la prochaine percée! ;)