Séquence d’échappement : comparaison de string et valeurs hexa
Aujourd’hui, nous allons parler développement C, des normes ANSI C et d’un fonctionnement surprenant des séquences d’échappement. Oui tout de suite ça ne donne pas forcément envie dit comme ça mais vous verrez que cet article a tout de même un certain intérêt.
Prérequis
L’ensemble du code C présent sur cette page est présent sur le dépôt Github c_compare_string et est compilé avec la commande suivante :
$ gcc main.c -o c_compare_string
Les séquences d’échappement
Nombre d’entre vous savent ce que sont les séquences d’échappement. Pour les autres, les séquences d’échappement permettent de définir un caractère par sa valeur hexadécimale, sa valeur octale ou encore sa représentation (par exemple \n
pour le retour à la ligne) et ce dans beaucoup de langages.
printf("\x31 \061 1\n"); > 1 1 1
print('\x31 \061 1\n'); > 1 1 1
Dans les exemples ci-dessus, nous sortons une chaînes constitué de 3 « 1 » qui se suivent (\x31 \061 1
), suivi d’un retour à la ligne (\n
).
La comparaison de chaînes
En C, pour comparer 2 chaines de caractères, nous avons 3 solutions, strcmp
, strncmp
ou encore memcmp
.
#include <string.h> int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); int memcmp(const void *s1, const void *s2, size_t n);
Les 2 premières comparent les chaines de caractères jusqu’à l’octet 0x00
qui est censé désigner la fin de chaine. Cependant strncmp
comparera au maximum le nombre de caractère spécifié dans le paramètre n
.
Enfin la dernière memcmp
comparera la valeur de l’ensemble des n premiers octets. La page de man spécifie que l’ensemble des octets seront interprété en unsigned char
.
Notre problématique
Sur un projet particulier, nous avons eu un soucis de devoir comparer 2 chaînes de caractères qui nous paraissaient identiques mais qui ne l’étaient pas au vue de memcmp
etstrncmp
. Un code parlant plus que mille mots.
#define STRING1 "\x3123toto" char buffer[10]; int rmc, rsc; // Prepare buffer buffer[0] = 0x31; buffer[1] = '2'; buffer[2] = '3'; buffer[3] = 't'; buffer[4] = 'o'; buffer[5] = 't'; buffer[6] = 'o'; buffer[7] = 0x00; // Compare le buffer à STRING1, on attend 0 en retour. rmc = memcmp(buffer, STRING1, 7); rsc = strncmp(buffer, STRING1, 7); printf("rmc: %d - rsc: %d - buffer: %s - STRING1: %s\n", rmc, rsc, buffer, STRING1); // rmc: 14 - rsc: 14 - buffer: 123toto - STRING1: #toto
On voit dans cet exemple que quelle que soit la fonction utilisée, elles nous renvoient que les chaînes sont différentes et voila la cause de cet article.
Les solutions
Après quelques essais, nous avons remarqué que les solutions suivantes fonctionnaient :
#define STRING2 "\x31" "23toto" #define STRING4 "\x31\x32\x33toto" ... /* Compare le buffer à STRING2, on attend 0 en retour. */ rmc = memcmp(buffer, STRING2, 7); rsc = strncmp(buffer, STRING2, 7); printf("> rmc: %d - rsc: %d - buffer: %s - STRING2: %s\n", rmc, rsc, buffer, STRING2); /* > rmc: 0 - rsc: 0 - buffer: 123toto - STRING2: 123toto */ /* Compare le buffer à STRING4, on attend 0 en retour. */ rmc = memcmp(buffer, STRING4, 7); rsc = strncmp(buffer, STRING4, 7); printf("rmc: %d - rsc: %d - buffer: %s - STRING4: %s\n", rmc, rsc, buffer, STRING4); /* > rmc: 0 - rsc: 0 - buffer: 123toto - STRING4: 123toto */
Sur ces 2 essais, les comparaisons fonctionnent. Mais pourquoi ?
Explication
Il s’avère que selon les normes ANSI C (et ce depuis au moins la C89), les séquences d’échappement hexadécimales intègrent tous les caractères pouvant entrer dans une représentation hexadécimale d’un nombre. Quant aux représentations octales, elles ne peuvent être composées de plus de 3 digits.
En gros, les représentations suivantes sont equales :
printf("\x0031 = \x000031 = \x00000031 = \61 = \061\n"); /* > 1 = 1 = 1 = 1 = 1 */
Le plus surprenant est que la représentation n’est finalement enregistrée que sur 1 octet.
char *test = "\x31323334\x00\x00\x00"; int i; for (i = 0; i < 4; ++i) { printf("\n> test[%d] = 0x%02X", i, test[i]); } /* > test[0] = 0x34 > test[1] = 0x00 > test[2] = 0x00 > test[3] = 0x00 */
Si vous aussi, vous êtes surpris par un comportement anecdotique de votre compilateur ou du préprocesseur, partagez le avec nous.