fonction setlocale en C

Le problème exposé dans ce sujet a été résolu.

Bonjour,

J’ai posté récemment et manifestement je me suis mal exprimé car personne n’a compris ma question. La discussion était devenu quasi illisible et donc je décide de faire un nouveau post… J’ai essayé autant que faire se peut de simplifier le problème. En espérant que ce soit plus clair maintenant :)

Voici le code qui me sert de support :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <locale.h>
#include <Windows.h>

#define TAILLE 100

void afficherChaine()
{
	char chaine[TAILLE] = "\xC3\x84"; //195 et 132 en base 10

	int k;
	for (k = 0 ; k < strlen(chaine) ; k++)
		printf("chaine[%d] : %X, %c\n", k, (unsigned char)chaine[k], chaine[k]);

	printf("\n");
}

int main()
{
	printf("GetConsoleOutputCP() : %d\n\n", GetConsoleOutputCP());

	char* encodage = setlocale(LC_ALL, "");
	if (encodage != NULL) printf("locale    : %s\n", encodage);
	afficherChaine();

	encodage = setlocale(LC_ALL, "C");
	if (encodage != NULL) printf("locale    : %s\n", encodage);
	afficherChaine();

	encodage = setlocale(LC_ALL, "Greek_Greece.1253");
	if (encodage != NULL) printf("locale    : %s\n", encodage);
	afficherChaine();

	return 0;
}

il retourne :

GetConsoleOutputCP() : 850

locale    : French_France.1252
chaine[0] : C3, Ã
chaine[1] : 84, "

locale    : C
chaine[0] : C3, ├
chaine[1] : 84, ä

locale    : Greek_Greece.1253
chaine[0] : C3, G
chaine[1] : 84, "

Il s’agit de tenter d’expliquer ce qu’on voit ici. Pourquoi a-t-on deux affichages différents en fonction de la locale ? Et surtout comment anticiper sur ce qu’on voit ici ? Je n’arrive pas à trouver un logique particulière à tout cela.

PRECISION TRES IMPORTANTE : " et „ sont deux caractères différents. Donc dans la table 1252 le " n’est pas le caractère 132.

Merci pour votre aide

+0 -0

Bonsoir,

Quand on disait sur l’autre sujet que setlocale n’a pas d’influence sur la sortie en console, ton exemple montre de toute évidence le contraire. Alors, à défaut d’explications logiques, essayons de décortiquer.

Premier exemple.
L’affichage de Ã, correspondant au caractère 195 de la table CP1252, est correct parce que la table par défaut pour le français correspond avec l’encodage actuel de ta console.
Au code 132 correspond bien „ en CP850 et CP1152.

Second exemple:
Le ├ correspond au caractère 195 de la table CP437
Le ä correspond aussi au code 132 de la table CP437

Troisième exemple:
Au code 195 de la table CP1253 correspond en réalité le gamma majuscule

IL y a donc bien une traduction de page de code quand on affiche des caractères en console. Par contre, si tu envoies la sortie vers un fichier, tu verras que setlocale n’a aucune influence, les octets 0xC3 et 0x84 seront trois fois inchangés.

Maintenant, pourquoi le „ et le gamma majuscule ne s’affichent pas ? Les deux ont un point de code unicode >255, donc ils ne peuvent pas s’afficher quand ta console est réglée en CP850 ou CP1252. Par contre ils sont correctement affichés si tu bascules la console en UTF-8 (chcp 65001). Plutot que d’afficher un vilain carré quand un caractère n’est pas affichable, il semble exister un mapping de caractères visuellement proches. G et " sont choisis à la place du gamma majuscule et de „.

En conclusion, tout le truc est assez foireux. Je fais l’hypothèse suivante:

  1. Tu envoies l’octet 195, qu’on suppose être en CP1252, car c’est l’encodage OEM natif de ton windows
  2. IL y a une traduction de CP1252 vers CP850, car le CP850 est l’encodage MS-DOS associé à la locale française
  3. IL y a une traduction de CP850 vers la page de code actuellement définie dans ta console, CP850 par défaut (donc pas de traduction), ou bien CP1252, ou encore CP65001 pour l’UTF-8.

Voilà. Je laisse les experts confirmer ou infirmer.

+1 -0

@QuentinC Je ne sais pas si tu as tout juste, mais j’ai réfléchis sur ta proposition et elle me semble très pertinente. En effet, je fais le test suivant :

int main()
{
	SetConsoleOutputCP(1252);
	printf("GetConsoleOutputCP() : %d\n\n", GetConsoleOutputCP());

	char* encodage = setlocale(LC_ALL, "French_France.1252");
	if (encodage != NULL) printf("locale    : %s\n", encodage);
	afficherChaine();

	return 0;
}

Je n’ai changé que le main, tout le reste est identique au code donné dans le message initial. Retour :

GetConsoleOutputCP() : 1252

locale    : French_France.1252
chaine[0] : C3, Ç
chaine[1] : 84, 

Cela apporte un élément flagrant allant dans ton sens, car 0xC3 (195) est un à dans la table 1252. Ce même caractère est codé C7 dans la table 850. Quand on revient dans la table 1252… Le 0xC7 est un Ç !!

Je pense qu’en effet cela prouve qu’en l’occurrence, j’essaye de tout mettre en 1252, pourtant il y a bien des traductions dans la page 850 qui viennent s’incruster toutes seules.

Je pousse le test plus loin :) J’ai choisi consciencieusement la table CP1258 pour ma locale (une table obscure peu utilisée), j’ai mis mon terminal en CP1252 et enfin j’ai envoyé le caractère 0xC1. Si tu veux faire le test, il faut changer ces lignes dans le programme : char chaine[TAILLE] = "\xC1";, char* encodage = setlocale(LC_ALL, "french_france.1258");.

C’est un Á en 1258, qui est converti en 850, ce qui donne 0xB5 pour le même caractère. Enfin, l’octet est déchiffré avec la console en 1252 ce qui donne un µ. Merveilleux, c’est ce que m’affiche mon terminal.

Pour reformuler ton idée :

  • on regarde la locale du programme et l’octet correspondant au caractère à afficher est éventuellement modifié pour correspondre à un caractère identique dans la table CP850.
  • la page de code en vigueur dans le terminal détermine comment sera affiché l’octet précédemment obtenu : on associe bêtement le caractère associé à l’octet dans la table et on l’affiche.

Merci pour votre aide aussi bien ceux qui ont aidé sur le précédent sujet et particulièrement à @QuentinC qui je pense m’a épargné quelques heures de recherches ^^

Si toutefois vous avez des ressources à proposer qui vont dans le détail de ce sujet je suis toujours preneur. En dépit de nombreuses recherches sur le net, je n’ai pas réussi à trouver de la documentation donnant une réponse "officielle" à ma question.

CONCLUSION : il est parfaitement inutile de changer la page de code de la console, cela n’amène que des complications. Je pense qu’il faut laisser la page en 850 par défaut, et mettre la locale du programme à french. Comme ça, Windows se charge tout seul de convertir les caractères dans la page du terminal et c’est réglé. De plus, il apparaît que la locale du programme n’affecte pas vraiment le comportement de printf, c’est Windows qui me semble-t-il, fait la conversion vers les caractères qui collent, compte tenue de la locale du programme. Cette conclusion est rassurante car en effet, je n’avais jamais vu nulle part que le comportement de printf était affecté par setlocale.

+0 -0

Hello,

Je ne suis pas sûr qu’il y ait une vraie réponse officielle, parce que des tonnes de programmes de windows lui-même gèrent mal les accents. Certains définissent la locale et du coup marchent bien quand la locale est en CP850, tandis que d’autres ne définissent pas la locale et marchent bien quand la console est en CP1252. C’est le gros bordel.

C’est peut-être pour ça qu’ils ont voulu remettre ça à neuf avec le windows terminal de windows 11.

ET sinon, si, normalement, le comportement de printf est affecté par la locale, de même que scanf. Essaie avec le format %g et une valeur double pour voir.

+0 -0

oui effectivement mais c’est assez marginal j’ai omis de le mentionner.

Ce n’est pas si marginal que ça. En fait le problème c’est surtout quand on essaie de lire une valeur double dans un fichier de configuration, fichier de configuration dont le format est censé être indépendant de la locale. Je peux t’affirmer que je me suis bien cassé la tête la première fois que ça m’est arrivé.

L’avantage c’est qu’on n’a plus ce problème en C++ quand on utilise les flux.

+1 -0

L’avantage c’est qu’on n’a plus ce problème en C++ quand on utilise les flux.

En C++, on imbue chaque flux indépendamment des autres avec la locale que l’on désire. Maintenant il y a aussi une locale globale et cela fait une éternité que je n’ai plus cherché à perdre du temps à gérer des accents sous Windows.

Les dernières fois, j’ai un vague souvenir d’injecter des code-convertion-facets qui prennent en charge la conversion des caractères entre l’encodage interne et celui du flux, à la volée. Ouais. Franchement compliqué pour si "peu".

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte