Une caractéristique intéressante de C par rapport à d'autres langages est que bon nombre de ses types de données sont basés sur la taille des mots de l'architecture cible, plutôt que d'être spécifiés en termes absolus. Bien que cela permette au langage d'être utilisé pour écrire du code sur des machines qui pourraient avoir des difficultés avec certains types, il est très difficile de concevoir du code qui s'exécutera de manière cohérente sur différentes architectures. Considérez le code:
uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;
Sur une architecture où int
est 16 bits (toujours vrai de nombreux petits microcontrôleurs) ce code attribuerait une valeur de 1 en utilisant un comportement bien défini. Sur les machines où int
est 64 bits, il attribuerait une valeur 4294836225, toujours en utilisant un comportement bien défini. Sur les machines où int
est 32 bits, il attribuerait probablement une valeur de -131071 (je ne sais pas si ce serait un comportement défini par l'implémentation ou indéfini). Même si le code n'utilise rien sauf ce qui est censé être des types "de taille fixe", la norme exigerait que deux types de compilateurs différents utilisés aujourd'hui donnent deux résultats différents, et de nombreux compilateurs populaires en rapportent aujourd'hui un troisième.
Cet exemple particulier est quelque peu artificiel, en ce que je ne m'attendrais pas à ce que le code du monde réel attribue directement le produit de deux valeurs 16 bits à une valeur 64 bits, mais il a été choisi comme un bref exemple pour montrer un entier de trois manières les promotions peuvent interagir avec des types non signés de taille supposée fixe. Il y a des situations du monde réel où il est nécessaire que les mathématiques sur des types non signés soient effectuées selon les règles de l'arithmétique mathématique des entiers, d'autres où il est nécessaire qu'elles soient effectuées selon les règles de l'arithmétique modulaire, et d'autres où elles ne le font vraiment pas '' t importe. Une grande partie du code du monde réel pour des choses comme les sommes de contrôle repose sur le uint32_t
mod de bouclage arithmétique 2³² et sur la possibilité d'effectuer des arbitrairesuint16_t
arithmétique et obtenir des résultats qui sont, au minimum, définis comme étant le mod 65536 précis (par opposition au déclenchement d'un comportement indéfini).
Même si cette situation semble clairement indésirable (et le deviendra davantage à mesure que le traitement 64 bits devient la norme à de nombreuses fins), le comité des normes C de ce que j'ai observé préfère introduire des fonctionnalités de langage qui sont déjà utilisées dans certaines productions notables. plutôt que de les inventer "à partir de zéro". Existe-t-il des extensions notables du langage C qui permettraient au code de spécifier non seulement comment un type sera stocké mais aussi comment il devrait se comporter dans des scénarios impliquant des promotions possibles? Je peux voir au moins trois façons dont une extension de compilateur peut résoudre ces problèmes:
En ajoutant une directive qui demanderait au compilateur de forcer certains types entiers "fondamentaux" à certaines tailles.
En ajoutant une directive qui demanderait au compilateur d'évaluer divers scénarios de promotion comme si les types de la machine avaient des tailles particulières, quelles que soient les tailles réelles des types sur l'architecture cible.
En autorisant des moyens de déclarer des types avec des caractéristiques spécifiques (par exemple, déclarer qu'un type doit se comporter comme un anneau algébrique enveloppant le mod-65536, quelle que soit la taille du mot sous-jacent, et ne doit pas être implicitement convertible en d'autres types; l'ajout d'un
wrap32
à unint
devrait produire un résultat de type,wrap32
qu'ilint
soit supérieur ou non à 16 bits, tout en ajoutant unwrap32
directement à unwrap16
devrait être illégal (car aucun ne pourrait se convertir à l'autre).
Ma propre préférence serait la troisième alternative, car elle permettrait même aux machines avec des tailles de mots inhabituelles de travailler avec beaucoup de code qui s'attend à ce que les variables soient «encapsulées» comme elles le feraient avec des tailles de puissance de deux; le compilateur peut devoir ajouter des instructions de masquage de bits pour que le type se comporte correctement, mais si le code a besoin d'un type qui enveloppe le mod 65536, il est préférable que le compilateur génère un tel masquage sur les machines qui en ont besoin que d'encombrer le code source avec lui ou simplement avoir un tel code inutilisable sur des machines où un tel masquage serait nécessaire. Je suis curieux, cependant, s'il existe des extensions communes qui permettraient d'obtenir un comportement portable via l'un des moyens ci-dessus, ou via des moyens auxquels je n'ai pas pensé.
Pour clarifier ce que je recherche, il y a quelques choses; notamment:
Bien qu'il existe de nombreuses façons d'écrire du code de manière à garantir la sémantique souhaitée (par exemple, définir des macros pour effectuer des calculs sur des opérandes non signés de taille particulière afin de produire un résultat qui enveloppe explicitement ou non) ou au moins empêche les indésirables la sémantique (par exemple, définir de manière conditionnelle un type
wrap32_t
pour êtreuint32_t
sur des compilateurs où auint32_t
ne serait pas promu, et comprendre qu'il vaut mieux pour le code qui nécessite l'wrap32_t
échec de la compilation sur des machines où ce type serait promu que de le faire s'exécuter et produire un comportement faux), s'il existe un moyen d'écrire le code qui jouerait le plus favorablement avec les futures extensions de langage, l'utiliser serait mieux que de concevoir ma propre approche.J'ai quelques idées assez solides sur la façon dont le langage pourrait être étendu afin de résoudre de nombreux problèmes de taille entière, permettant au code de générer une sémantique identique sur des machines avec des tailles de mots différentes, mais avant de passer beaucoup de temps à les écrire, je voudrais de savoir quels efforts dans ce sens ont déjà été entrepris.
Je ne souhaite en aucun cas être perçu comme dénigrant le Comité des normes C ou le travail qu'il a produit; Je m'attends cependant à ce que d'ici quelques années, il devienne nécessaire de faire fonctionner correctement le code sur les machines où le type de promotion "naturel" serait 32 bits, ainsi que celles où il serait 64 bits. Je pense qu'avec quelques extensions modestes de la langue (plus modestes que la plupart des autres changements entre C99 et C14), il serait possible non seulement de fournir un moyen propre d'utiliser efficacement les architectures 64 bits, mais dans le marché de faciliter également l'interaction avec les machines de "taille de mot inhabituelle" que la norme a historiquement recourbées pour prendre en charge [par exemple, permettant à une machine avec un char
code 12 bits d'exécuter du code qui attend unuint32_t
pour envelopper le mod 2³²]. Selon la direction que prendront les futures extensions, je m'attendrais également à ce qu'il soit possible de définir des macros qui permettraient au code écrit aujourd'hui d'être utilisable sur les compilateurs d'aujourd'hui où les types entiers par défaut se comportent comme "attendus", mais également utilisable sur les futurs compilateurs où l'entier les types seraient par défaut se comporter différemment, mais où peuvent fournir les comportements requis.
la source
int
est plus grand queuint16_t
, les opérandes de la multiplication seraient promusint
et la multiplication serait effectuée commeint
multiplication, et laint
valeur résultante serait convertie enint64_t
pour l'initialisation dewho_knows
.int
, mais il se faufile toujours. (Encore une fois, en supposant que ma compréhension de la norme C est correcte.)Réponses:
Comme l'intention typique d'un code comme celui-ci
consiste à effectuer la multiplication en 64 bits (la taille de la variable dans laquelle le résultat est stocké), la manière habituelle d'obtenir le résultat correct (indépendant de la plate-forme) consiste à transtyper l'un des opérandes pour forcer une multiplication à 64 bits:
Je n'ai jamais rencontré d'extensions C qui rendent ce processus automatique.
la source
uint32_t
ou utiliser une macro définie comme étant#define UMUL1616to16(x,y)((uint16_t)((uint16_t)(x)*(uint16_t)(y)))
soit en#define UMUL1616to16(x,y)((uint16_t)((uint32_t)(x)*(uint16_t)(y)))
fonction de la taille deint
), mais plutôt s'il existe toute norme émergente sur la façon de gérer ces choses de manière utile plutôt que de définir mes propres macros.(ushort1*ushort2) & 65535u
serait d'effectuer une arithmétique mod-65536 pour toutes les valeurs d'opérande. En lisant la justification de C89, je pense qu'il est assez clair que même si les auteurs ont reconnu qu'un tel code pourrait échouer sur certaines implémentations si le résultat dépassait 2147483647, ils s'attendaient à ce que ces implémentations deviennent de plus en plus rares. Cependant, ce code échoue parfois sur gcc moderne.