Ne serait-il briser le code langue ou existant si nous ajouterions en toute sécurité signé / non signé compare à C / C ++?

StackOverflow https://stackoverflow.com/questions/3476590

Question

Après avoir lu cette question sur la compare signé / non signé (ils viennent tous les deux jours, je dirais):

Je me suis demandé pourquoi nous ne l'avons pas signé non signé approprié compare et place ce horrible gâchis? Prenez la sortie de ce petit programme:

#include <stdio.h>
#define C(T1,T2)\
 {signed   T1 a=-1;\
 unsigned T2 b=1;\
  printf("(signed %5s)%d < (unsigned %5s)%d = %d\n",#T1,(int)a,#T2,(int)b,(a<b));}\

 #define C1(T) printf("%s:%d\n",#T,(int)sizeof(T)); C(T,char);C(T,short);C(T,int);C(T,long);
int main()
{
 C1(char); C1(short); C1(int); C1(long); 
}

Compilé avec mon compilateur standard (gcc, 64 bits), je reçois ceci:

char:1
(signed  char)-1 < (unsigned  char)1 = 1
(signed  char)-1 < (unsigned short)1 = 1
(signed  char)-1 < (unsigned   int)1 = 0
(signed  char)-1 < (unsigned  long)1 = 0
short:2
(signed short)-1 < (unsigned  char)1 = 1
(signed short)-1 < (unsigned short)1 = 1
(signed short)-1 < (unsigned   int)1 = 0
(signed short)-1 < (unsigned  long)1 = 0
int:4
(signed   int)-1 < (unsigned  char)1 = 1
(signed   int)-1 < (unsigned short)1 = 1
(signed   int)-1 < (unsigned   int)1 = 0
(signed   int)-1 < (unsigned  long)1 = 0
long:8
(signed  long)-1 < (unsigned  char)1 = 1
(signed  long)-1 < (unsigned short)1 = 1
(signed  long)-1 < (unsigned   int)1 = 1
(signed  long)-1 < (unsigned  long)1 = 0

Si je compile 32 bits, le résultat est le même, sauf que:

long:4
(signed  long)-1 < (unsigned   int)1 = 0

Le "Comment?" de tout cela est facile à trouver: juste de la section 6.3 de la norme ou le chapitre 4 de C99 C ++ et déterrer les clauses qui décrivent la façon dont les opérandes sont convertis en un type commun et cela peut se briser si les réinterprète de type commun de valeurs négatives <. / p>

Mais qu'en est- « Pourquoi? ». Comme on peut le voir, le « < » échoue dans 50% des cas, aussi dépend de la taille de béton des types, il est donc dépendant de la plateforme. Voici quelques points à considérer:

  • Le converti et processus comparer est pas vraiment un excellent exemple pour la Règle de Surprise moins

  • Je ne crois pas qu'il y ait un code là-bas, qui repose sur la proposition selon laquelle (short)-1 > (unsigned)1 et est pas écrite par des terroristes.

  • Tout cela est terrible quand vous êtes en C ++ avec le code de modèle, car vous avez besoin de taper trait magique pour tricoter un correct "<".


Après tout, signé et comparant la valeur non signée de différents types facile à mettre en œuvre:

signed X < unsigned Y -> (a<(X)0) || ((Z)a<(Z)b) where Z=X|Y 

La pré-check est pas cher et peut également être optimisé loin par le compilateur si> = 0 peut être prouvé statiquement.

Alors, voici ma question:

Ne serait-il briser la langue ou le code existant si nous ajouterions en toute sécurité signé / non signé compare à C / C ++?

( « Ne serait-il briser la langue » signifie que nous aurions besoin de faire des changements massifs dans les différentes parties de la langue pour accueillir ce changement)


Mise à jour: J'ai couru sur mon bon vieux Turbo-C ++ 3.0 et obtenu cette sortie:

char:1
(signed  char)-1 < (unsigned  char)1 = 0

Pourquoi est-(signed char)-1 < (unsigned char) == 0 ici?

Était-ce utile?

La solution

Oui, il romprait le code de langue / existant. La langue, comme vous l'avez noté, précise attentivement le comportement lorsque signés et non signés opérandes sont utilisés ensemble. Ce comportement des opérateurs de comparaison est essentielle pour certains idiomes importants, comme:

if (x-'0' < 10U)

Sans parler des choses comme (comparaison de l'égalité):

size_t l = mbrtowc(&wc, s, n, &state);
if (l==-1) ... /* Note that mbrtowc returns (size_t)-1 on failure */

En aparté, en spécifiant le comportement « naturel » pour les comparaisons mixtes signés / non signés encourrait également une pénalité de performance importante, même dans les programmes qui utilisent actuellement ces comparaisons de façon sécuritaire où ils ont déjà leur comportement « naturel » en raison de contraintes sur l'entrée que le compilateur aurait du mal à déterminer (ou pourrait ne pas être en mesure de déterminer tout). En écrivant votre propre code pour gérer ces tests, je suis sûr que vous avez déjà vu ce que la peine de performance ressemblerait, et ce n'est pas assez.

Autres conseils

Ma réponse est C seulement.

Il n'y a pas de type en C qui peut accueillir toutes les valeurs possibles de tous les types entiers possibles. Le plus proche C99 vient à c'est intmax_t et uintmax_t, et leur intersection ne couvre que la moitié de leur gamme respective.

Par conséquent, vous ne pouvez pas mettre en œuvre une comparaison de la valeur mathématique de tels que x <= y en convertissant d'abord x et y à un type commun et faire ensuite une opération simple. Ceci est un changement majeur d'un principe général de la façon dont fonctionnent les opérateurs. Il brise aussi l'intuition que les opérateurs correspondent à des choses qui ont tendance à être des instructions simples dans le matériel commun.

Même si vous avez ajouté cette complexité supplémentaire à la langue (et le fardeau supplémentaire pour les auteurs de mise en œuvre), il ne serait pas avoir des propriétés très agréable. Par exemple, x <= y ne serait toujours pas équivalent à x - y <= 0. Si vous vouliez toutes ces propriétés agréables, vous auriez à faire partie des entiers de taille arbitraire de la langue.

Je suis sûr qu'il ya beaucoup de vieux code unix là-bas, peut-être un peu en cours d'exécution sur votre machine, qui suppose que (int)-1 > (unsigned)1. (Ok, peut-être il a été écrit par les combattants de la liberté; -)

Si vous voulez Lisp / haskell / python / favorite_language_with_bignums_built_in $, vous savez où le trouver ...

Je ne pense pas que ce serait briser la langue, mais oui, il pourrait casser un code existant (et la rupture serait probablement difficile à détecter au niveau du compilateur).

Il existe beaucoup plus de code écrit en C et C ++ que vous et moi ensemble peut imaginer (certaines d'entre elles peuvent même être écrit par des terroristes).

En se fondant sur « proposition selon laquelle (short)-1 > (unsigned)1 » peut être fait sans le vouloir par quelqu'un. Il existe beaucoup de code C GÉRER manipulation de bits complexes et les choses semblables. Il est tout à fait possible un programmeur peut être utiliser le comportement de comparaison actuelle dans ce code. (D'autres personnes ont déjà fourni des exemples de belles tel code, et le code est encore plus simple que je me attends).

solution actuelle est de mettre en garde sur ces comparaisons à la place, et de laisser la solution au programmeur, qui je pense est dans un esprit comment C et C ++ fonctionne. En outre, la résolution sur un niveau de compilateur encourra une pénalité de performance, ce qui est quelque chose de C et les programmeurs C + sont extrêmement sensibles à. Deux essais au lieu d'un peut sembler un problème mineur pour vous, mais il y a probablement beaucoup de codes C où ce serait un problème. Il pourrait être résolu par exemple en forçant le comportement précédent en utilisant des moulages explicites à un type de données commun - mais cela à nouveau besoin programmeur attention, donc il n'y a pas mieux qu'un simple avertissement

.

Je pense que C ++ est comme l'empire romain. Son grand et aussi mis en place pour corriger les choses qui vont le détruire.

c ++ 0x - et coup de pouce - sont des exemples d'une syntaxe horribles horribles - le genre de bébé que ses parents peuvent aimer - et sont un long chemin à long de l'élégante simple (mais sévèrement limité) c ++ il y a 10 ans.

Le point est, au moment où on a « fixe » quelque chose d'aussi terriblement simple que les comparaisons des types entiers, assez héritage et le code existant c ++ a été rompu que l'on pourrait tout aussi bien appeler une nouvelle langue.

Et une fois rompu, il y a tant d'autre qui est également admissible à la fixation rétroactive.

La seule façon pour une langue de définir des règles qui peuvent se rapprocher de respecter le principe de Surprise moins au moment de l'exécution lors de l'utilisation combinant opérandes de différents types de langue C serait d'avoir soit le compilateur interdit les conversions de type implicites au moins certains contextes (déplacement de la « surprise » à « pourquoi ne cette compilation? » et rendant moins susceptibles de causer des bugs inattendus sur la route), définir plusieurs types pour chaque format de stockage (par exemple à la fois l'emballage et des variantes non emballage de chaque type entier), ou les deux.

Ayant plusieurs types pour chaque format de stockage, par exemple, à la fois l'emballage et les versions non-emballage de signés et non signés entiers de 16 bits, pourrait permettre au compilateur de distinguer entre «J'utilise une valeur 16 bits ici au cas où il rend les choses plus efficaces, mais il ne va jamais dépasser la plage 0-65535 et je ne voudrais pas ce qui est arrivé si elle a fait ) » et « J'utilise une valeur 16 bits qui doit envelopper 65535 il devient négatif ». Dans ce dernier cas, un compilateur qui a utilisé un registre 32 bits pour la valeur telle aurait à masquer après chaque opération arithmétique, mais dans le premier cas, le compilateur pourrait omettre cela. En ce qui concerne votre souhait particulier, le sens d'une comparaison entre un non-emballage signé long et non-emballage unsigned long serait clair, et il serait approprié pour un compilateur pour générer le multi séquence d'instructions nécessaires pour y arriver (depuis la conversion d'un nombre négatif à une unsigned long non-emballage serait un comportement non défini, ayant le compilateur de définir un comportement pour les opérateurs de comparaison sur ces types ne sont pas en conflit avec tout ce qui peut être spécifié).

Malheureusement, au-delà d'avoir le compilateur générer des avertissements pour les comparaisons opérandes mixtes, je ne vois pas vraiment beaucoup qui peut être fait avec le langage C tel qu'il existe sans ajouter de nouveaux types telle qu'elle est décrite ci-dessus; même si je considère l'ajout de ces nouveaux types comme une amélioration, je ne retiendrais pas mon souffle.

Si une comparaison entre les types entiers comparé les valeurs mathématiques réelles, je voudrais la même chose pour arriver à des comparaisons entre les nombres entiers et à virgule flottante. Et en comparant les valeurs exactes d'un entier de 64 bits arbitraire et un nombre à virgule flottante double précision arbitraire est assez difficile. Mais le compilateur serait probablement mieux à ce que moi.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top