KTP - Exemple de génération de clé CD

cs_christophedlr Messages postés 267 Date d'inscription samedi 3 janvier 2004 Statut Membre Dernière intervention 23 août 2023 - 4 janv. 2014 à 01:28
cs_christophedlr Messages postés 267 Date d'inscription samedi 3 janvier 2004 Statut Membre Dernière intervention 23 août 2023 - 5 janv. 2014 à 17:42
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.

https://codes-sources.commentcamarche.net/source/100327-ktp-exemple-de-generation-de-cle-cd

cs_christophedlr Messages postés 267 Date d'inscription samedi 3 janvier 2004 Statut Membre Dernière intervention 23 août 2023 5
5 janv. 2014 à 17:42
MErci pour tes précieux conseils.

Pour le C++11, je ne sais pas si ma version actuelle de GCC le gère en totalité ou en partie. Mais surtout, ça risque pas d'être incompatible avec les vieux systèmes ? Car mon but est que mes programmes passent sur les plus vieux windows possible (au moins XP).
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
5 janv. 2014 à 17:14
Mon programme qui (comme je l'ai souligné), utilisera des fichiers XML définissant comment la clé est créée.
Pour l'autre se sera différent, aussi il utilisera une lib (libxml2) pour le XML.
Si tu en as la possiblité, évite le XML. C'est une mode du début des années 2000, de moins en moins utilisée. C'est lourd à parser, très verbeux, et aisément remplaçable par autre chose. Par exemple pour des fichiers de configurations, on préfère mettre des couples clés/valeurs, voir une base locale SQLite (Si tu ne connais pas, c'est du SQL qui tient dans un fichier local, sans utiliser de serveur). Dans le milieu du web, on a depuis longtemps abandonné XML au profit de Json, bien plus compacte et rapide à parser. A moins d'y être contraint, le format XML est rarement une bonne idée...

Pour ton cas particulier, un simple fichier de texte contenant ta ou tes regexp(s) sera suffisant.

Pour la méthode random, j'ai lu sur le net qu'il fallait des integer, d'où le fait que je n'ai pas passé de char mais des integer (et donc les dits commentaires). Merci donc pour l'information.
Les types "pod" (int, char, float, etc...) sont compatibles entre eux sans cast. Tant que tu fais rentrer un type plus petit dans un plus grand, il n'y aura jamais de souci. Faire rentrer un char dans int est sans danger, l'inverse n'est pas vrai (valeur tronquée).

Quand aux cast, je connais ceux en C mais hélas pas ceux en C++ (static_cast dont j'ai entendu parlé mais jamais réussi à l'utiliser).
Tu as 4 casts en C++:
- reinterpret_cast: Permet de transformer un type en tout autre chose. Exemple classique: fait passer un pointeur "void*" dans un entier "int". Il est rare qu'ont ait à l'utiliser. C'est à employer avec grande parcimonie.
- static_cast: C'est le plus courant. Il transforme un type en un autre. Exemple: "void*" en "int*", ou "float*" en "int*".
- const_cast: Permet de faire sauter un const. Exemple: "const char*" vers "char*". Son utilisation est signe de mauvaise conception dans 99% des cas.
- dynamic_cast: Tente de convertir un pointeur sur classe en un autre compatible. Exemple: Si B hérite de A, alors on peut faire un dynamic_cast de B* vers A*. Si on tente un dynamic_cast sur quelque chose de non compatible, renvoi 0. Opération couteuse à éviter, puisque c'est le seul cast qui est fait à l'exécution et non à la compilation.

Le seul qui est vraiment "gratuit" et qui est le moins dangereux, c'est le static_cast. Par défaut, si tu ne sais pas quoi faire, utiliser un static_cast.
Pour info, le C cast est un "fourre-tout", qui réalise les casts décrits précédemment dans cet ordre là:
* const_cast
* static_cast
* static_cast puis const_cast
* reinterpret_cast
* reinterpret_cast puis const_cast

On préfère généralement avoir une erreur de compilation, plutôt que d'arriver silencieusement dans le dernier cas.

Exemple de cast C vs C++:
int* i = 0;
char* s = 0;

i = (char) s; // C
i = static_cast<char*>(s); // C++


Si tu as d'excellents liens sur le C++, je suis preneur.
Plus que des liens, il y a surtout des livres réputés sur le C++. Je te link des références de bouquins par MP.

Si tu en as la possibilité, je t'invite fortement à passer au C++11 (la nouvelle version du C++ officialisée comme son nom l'indique en 2011). C'est entièrement compatible avec le C++ standard, mais ça ajoute énormement de fonctionnalités en plus, corrige des bugs historiques (le fameux bug de parsing du "> >" dans un template), et améliore les performances (via les std::move).

Un exemple tout bête qui change la vie.
Avant:
std::vector<int> tab;
for (std::vector<int>::const_iterator it = tab.begin(); it != tab.end(); ++it)
  std::cout << *it << std::endl;


Après:
std::vector<int> tab;
for (int& i : tab)
  std::cout << i << std::endl;


La liste des fonctionnalités ici: http://en.wikipedia.org/wiki/C%2B%2B11
cs_christophedlr Messages postés 267 Date d'inscription samedi 3 janvier 2004 Statut Membre Dernière intervention 23 août 2023 5
Modifié par cptpingu le 5/01/2014 à 17:31
Bonjour à toi,

Merci pour ce très long commentaire. Et si tu me le permet je souhaite te répondre.

Concernant les regexp et le fait d'utiliser un fichier de définition plutôt que du codé en dur, c'est ce qui est prévu sur mon "vrai" programme. En fait, je me suis toujours demandé comment créer un système de génération et de vérification de clé CD (système qui plus tard sera utilisé sur mes programmes, mais pas dans un but de protection mais simplement d'apporter des fonctionnalités ou aide supplémentaire suivant si on a la clé ou non) ; KTP n'est là que pour la mise en pratique de cette interrogation, et devait à la base n'être que pour moi, afin de m'en servir comme modèle pour créer mon programme qui (comme je l'ai souligné), utilisera des fichiers XML définissant comment la clé est créée.
Simplement, comme j'y ai appris pas mal de choses au milieu, je me suis dit que mettre le programme ici serait une bonne idée. ;)
Par contre, je souhaite ne pas dépendre d'une bibliothèque externe, aussi dans le cas de ce projet j'ai choisie de tout faire avec l'API standard. Pour l'autre se sera différent, aussi il utilisera une bibliothèque (libxml2) pour le XML et certainement Boost pour d'autres trucs dont la génération aléatoire en effet.

Pour le if, si j'utilise le point d'exclamation au lieu du == false, il faisait l'inverse ; je ne saurais te dire d'ailleurs pourquoi, mais c'est pour ça que je me suis rabattu sur ce que j'ai fait.

Pour les extensions, je suis effectivement habitué au .h plutôt que .hpp, d'où le fait que j'utilise toujours .h, mais j'ai rapidement pris la bonne habitude du .cpp au lieu du .c ; va savoir pourquoi j'arrive sur l'un et l'autre non lol.

Pour les types je ne savais pas, ayant appris tout seul, j'ai vu les int etc. mais je connaissais pas les autres types dont tu parles ; merci pour l'information.

Pour la méthode random, j'ai lu sur le net qu'il fallait des integer, d'où le fait que je n'ai pas passé de char mais des integer (et donc les dits commentaires). Merci donc pour l'information.


Pour le reste, merci pour les conseils et surtout les liens.
Quand aux cast, je connais ceux en C mais hélas pas ceux en C++ (static_cast dont j'ai entendu parlé mais jamais réussi à l'utiliser). Si tu as d'excellents liens sur le C++, je suis preneur.
cptpingu Messages postés 3837 Date d'inscription dimanche 12 décembre 2004 Statut Modérateur Dernière intervention 28 mars 2023 123
4 janv. 2014 à 16:54
Bonjour.

== Au niveau fonctionnel ==

Le code respecte bien les spécifications. Néanmoins, c'est dommage que ce soit en "dur". Un axe d'amélioration intéressant, aurait été d'avoir un fichier de "description", suivant les conventions décrites dans "codages_specs.txt", et que le progamme s'adapte dynamiquement à ces règles. A vrai dire, on pourrait très bien remplacer ce fichier de specs par une expression rationnelle, qui s'acquitte parfaitement de cette tâche. Au final, ton programme peut se simplifier en "vérifier que la regex est bonne" et "générer un pattern compatible avec la regex" (c'est un simple automate, très simple à réaliser). En utilisant du boost::regex, on peut surement simplifier le code énormément, tout en le rendant plus dynamique.
C'est un axe d'amélioration qui a du potentiel, mais qui s'éloigne de ce que tu as fait (la simple utilisation de boost::regex, serait de la "triche", puisqu'on pourrait tout faire en deux fonctions).
Enfin, pour générer ce genre de clé, plutôt que du C++ on utilisera plutôt du sed ou du perl, qui réaliseront ce que tu as fait en 2 lignes.



== Au niveau technique ==

Y a des petites inélégances techniques, notamment sur l'utilisation de C au lieu de C++. Si je ne le détaille pas, je parle de C++03 (classique), sinon je parle de C++11.

- D'une manière générale, en C++ on utilise std::string et non char*. Sa simple présence, bien que possible, est généralement suspecte.
- Si on a besoin d'un tableau de caractère à taille fixe, on peut utiliser std::array (C++11).
- Quand on compare un booléen, on écrit "if (!val)" ou "if (val)" plutôt que "if (val == false)" ou "if (val == true)".
- On préfère 0 à NULL en C++. (Voir mieux: le mot clé "nullptr", à partir de C++11). Voir: http://0217021.free.fr/portfolio/axel.berardino/articles/null-en-cpp
- Les extensions de fichier en C++ sont ".cc/.hh" ou ".cpp/.hpp". Le ".h" étant plutôt reservé au C.
- Pour des raisons de portabilité, on préfère généralement utiliser les types "uint32_t", "uint16_t", etc... Plutôt que "unsigned int", "unsigned short", etc... Tu peux trouver ces types dans <stdint.h>. C'est une remarque facultative. C'est généralement une problématique rencontrée dans des cas assez particuliers (compilation sur des architectures différentes).
- A partir de C++11, on a enfin un vrai générateur de nombre aléatoire ! Si tu y accès, tu peux avantageusement remplacer le vieux couple srand/rand, par un std::mersenne_twister (ou tout autre algo de génération de nombre aléatoire fournit par la nouvelle version du C++).
- Au niveau lisibilité, tu as bien compris que "random(66, 72);" n'était pas très explicite, raison pour laquelle tu as ajouté des commentaires du type "//B-H". Néanmoins, on peut se passer de tout ces commentaires, et directement écrire quelque chose de lisible. Etant donné que 66 et 'B', c'est la même chose en C et C++, tu peux écrire: "random('B', 'H');". La même remarque s'applique à tous les "if" et "random()" de ton code.
- Toutes les classes héritant de std::fstream, se "close()" automatiquement au sortir d'un scope. Le "stream.close();" n'est donc pas nécessaire.
- Plutôt qu'un std::fstream + std::ios::out, on peut utiliser directement un std::ofstream. Même remarque pour les std::ifstream.
Ex:
std::fstream stream;

stream.open(file, std::fstream::in);
while ( stream.eof() != true )
{
  stream.getline(buffer, 20);
  validate(buffer);
}
stream.close();

peut être remplacé par:
std::ifstream stream(file);
while (stream)
{
  stream.getline(buffer, 20);
  validate(buffer);
}

On utiliserait d'ailleurs plutôt:
std::string line;
std::ifstream stream(file);
while (std::getline(stream, line))
  validate(line.c_str());

- Dans les fonctions de type "CValide::grp", tu copies des valeurs dans un tableau temp, ce qui peut être évité. De plus, le cast n'est pas nécessaire.
- Juste pour précision, en C++, on évite les "C cast" au profit des cast C++ dont le comportement est sélectionnable (il y a 4 casts différents, à raison).



== Au niveau architecture ==

Je vais parler ici, de l'agencement du code, et du design des classes et fonctions.

- Quand une méthode ne modifie pas un attribut, on doit la marquer comme const. (Exemple: les méthodes CValide::grp).
- Quand une méthode n'utilise pas d'attribut de classe ou n'appelle pas une autre méthode, alors il y a erreur de design. La méthode devrait être une fonction interne, visible uniquement par la classe. Pour réaliser cela, on met la fonction dans un namespace anonyme, dans le fichier d'implémentation de la classe (au-dessus des autres méthodes, pour qu'elle soit "vue"). C'est par exemple le cas pour la fonction "random".
- Dans le main, toutes les fonctions devraient être au dessus du main, dans un namespace anonyme.
- Plutôt que plein de "if" à la suite, il y a plusieurs endroits où il est plus adapté d'y mettre des "switch". Ce n'est d'ailleurs pas simplement plus élégant, mais aussi plus rapide.
- Quitte à utiliser du C pour lire les options de la ligne de commande autant le faire franchement. Il y a la méthode "getopt", qui lit déjà des options à partir de argc, argv, en gérant tous les cas (ordre des paramètres, doublon, etc...).
http://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
En C++, on utilise géneralement "boost::program_option". http://www.boost.org/doc/libs/1_55_0/doc/html/program_options/tutorial.html
Rejoignez-nous