C n'est plus un langage de programmation

Récemment, un article d'Aria Beingessner, vétéran de Rust et Swift, "C n'est plus un langage de programmation", a provoqué une discussion animée sur Hacker News.

Lien d'origine : https://gankra.github.io/blah/c-isnt-a-language/

Section des commentaires de Hacker News : https://news.ycombinator.com/item?id=30704642

Aria et son ami Phantomderp ont convenu de "très déçus de l'interface C ABI et d'essayer de la réparer". Mais Aria et ses amis ont des avis différents sur les raisons de sa déception. Quelles différences spécifiques ont surgi ? Pourquoi faire valoir que C n'est plus un langage de programmation ? L'auteur a compilé le texte original:

Organiser | Yu Xuan       

Produit | Durée de vie du programme (ID : coder _life)

Phantomderp essaie d'améliorer nativement l'utilisation de C lui-même comme langage de programmation, tandis qu'Aria veut améliorer l'utilisation de tout langage autre que C.

À ce moment, tout le monde aura des questions, qu'est-ce que ce problème a à voir avec C ?

Aria a déclaré: Si C est vraiment un langage de programmation, cela n'a rien à voir avec cela. Malheureusement, ce n'est pas le cas. Ce n'est pas le fait qu'il y ait des milliards d'implémentations et une hiérarchie d'échecs qui conduisent à sa mauvaise définition, c'est le fait que C est élevé à un rôle de prestige et de pouvoir dont le règne est absolu et éternel. C est le langage général de programmation, nous devons tous apprendre C, donc C n'est plus seulement un langage de programmation, c'est devenu un protocole que chaque langage de programmation général doit respecter.

C'est en fait un peu comme à propos de l'ensemble "C est un gâchis insaisissable défini par l'implémentation". Mais juste parce que cela nous a obligés à utiliser ce protocole, c'est devenu un cauchemar encore plus grand.

Interface de fonction externe

Parlons ensemble des problèmes techniques. À condition que vous ayez fini de concevoir votre nouveau langage Bappyscript, il existe un support de première classe pour Bappy Paws/Hooves/Fins. C'est un langage étonnant qui va révolutionner la façon dont les chats, les moutons et les requins sont programmés.

Mais maintenant, il doit réellement faire quelque chose d'utile. Comme accepter l'entrée ou la sortie de l'utilisateur, ou littéralement tout observable ou quelque chose. Si vous voulez que les programmes écrits dans ce langage soient compatibles avec les systèmes d'exploitation courants, vous devez interagir avec l'interface du système d'exploitation. J'ai entendu dire que tout sur Linux est "juste un fichier", alors ouvrons un fichier sur Linux ensemble !

OPEN(2)


NAME
       open, openat, creat - open and possibly create a file


SYNOPSIS


       #include <fcntl.h>


       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);


       int creat(const char *pathname, mode_t mode);


       int openat(int dirfd, const char *pathname, int flags);
       int openat(int dirfd, const char *pathname, int flags, mode_t mode);
       /* Documented separately, in openat2(2): */
       int openat2(int dirfd, const char *pathname,
                   const struct open_how *how, size_t size);


   Feature Test Macro Requirements for glibc (see
   feature_test_macros(7)):


       openat():
           Since glibc 2.10:
               _POSIX_C_SOURCE >= 200809L
           Before glibc 2.10:
               _ATFILE_SOURCE

C'est Bappyscript, pas C, alors où est l'interface Bappyscript pour Linux ?

Que voulez-vous dire en disant qu'il n'y a pas d'interface Bappyscript sous Linux ? Eh bien, bien sûr parce que c'est un tout nouveau langage, mais vous allez en ajouter un, n'est-ce pas ? Ensuite, vous constaterez que vous semblez devoir utiliser ce qu'ils donnent.

Vous aurez besoin d'une sorte d'interface pour permettre au langage d'appeler des fonctions externes, comme l'interface de fonction étrangère FFI. Ensuite, vous constatez que Rust a des FFI C, Swift en a et même Python en a.

04c6c2685e57128193bcaf79b094a53f.png

Vous constaterez que tout le monde doit apprendre le C pour communiquer avec les principaux systèmes d'exploitation, puis tout à coup tout le monde utilise le C lorsqu'il a besoin de se parler. Alors... pourquoi ne pas simplement utiliser C pour se parler ?

Maintenant, C est devenu un langage de programmation, pas seulement un langage de programmation, c'est aussi un protocole.

Que comprend une conversation avec C ?

Il est clair que fondamentalement, chaque langage doit apprendre à parler au C, et ce langage est définitivement très explicite.

Que signifie "dialogue" C ? Cela signifie obtenir une description des types d'interface et des fonctions sous la forme d'un fichier d'en-tête C, et d'une certaine manière :

  • correspondre à ces types de mises en page

  • Faites quelque chose avec l'éditeur de liens qui résout le symbole de la fonction en un pointeur

  • appelez ces fonctions avec l'ABI approprié (comme mettre args dans les bons registres)

Alors, voici quelques questions :

  • Vous ne pouvez pas réellement écrire un analyseur C

  • C n'a pas vraiment d'ABI, pas même une disposition de type définie

Impossible d'analyser un fichier d'en-tête C

Aria a affirmé que l'analyse C est fondamentalement impossible, mais certains disent qu'il existe en fait de nombreux outils capables de lire les en-têtes C, tels que rust-bindgen. Est-ce vraiment le cas ? pas vraiment.

bindgen utilise libclang pour analyser les fichiers d'en-tête C et C++. Pour modifier la façon dont bindgen recherche libclang, consultez la documentation de clang-sys. Pour plus de détails sur la façon dont bindgen utilise libclang, consultez le guide de l'utilisateur de bindgen.

Quiconque passe beaucoup de temps à essayer d'analyser rapidement un fichier d'en-tête C(++) abandonnera rapidement et laissera un compilateur C(++) faire le travail. N'oubliez pas que l'analyse significative des en-têtes C n'est pas seulement une analyse : vous devez également résoudre les #includes, les typedefs et les macros ! Alors maintenant, non seulement vous devez implémenter toutes les fonctions associées, mais également implémenter la logique d'analyse du fichier d'en-tête pour toutes les plates-formes, et vous devez également trouver des moyens de trouver DEFINED !

Prenez Swift, par exemple, il a des avantages absolus en termes d'interopérabilité C et de ressources, c'est un langage de programmation développé par Apple, qui a effectivement remplacé Objective-C comme langage principal pour définir et utiliser les API système sur sa plate-forme. Ce faisant, il pousse les concepts de stabilité et de conception ABI un peu plus loin que quiconque.

C'est aussi l'un des langages les plus compatibles FFI qu'Aria ait jamais vus. Il peut importer nativement des en-têtes (Objective-)C(++) et produire une belle interface Swift native dont les types sont automatiquement "liés" à leurs équivalents Swift aux limites (puisque les types ont le même ABI, est généralement transparent).

Swift est également développé par de nombreuses personnes chez Apple qui construisent et maintiennent Clang et LLVM. Ce sont les meilleurs experts mondiaux du C et de ses dérivés. L'un d'eux est Doug Gregor, qui a un jour exprimé son point de vue sur C FFI :

eea4271236fe7e9fa93db5a98192964d.png

Toutes ces raisons expliquent pourquoi Swift utilise Clang en interne pour gérer l'ABI C(++). De cette façon, nous n'avons pas à chasser chaque nouvelle propriété que Clang ajoute et qui affecte l'ABI.

Comme vous pouvez le voir, même Swift ne veut pas perdre de temps à analyser les en-têtes C(++). Donc, si vous ne voulez absolument pas que le compilateur C analyse et résolve les fichiers d'en-tête au moment de la compilation, comment faites-vous ?

Vous avez besoin d'une traduction manuelle ! int64_t? Ou écrire i64. long...? Qu'est-ce qu'un long ?

C n'a pas réellement d'ABI

Eh bien, il n'y a pas de surprise ici : les types entiers en C, qui sont conçus pour avoir une taille d'oscillation pour la « portabilité », sont en fait de taille instable. Nous pouvons penser que CHAR_BIT est bizarre, mais cela ne nous aide pas non plus à comprendre longla taille et l'alignement.

Certains disent que chaque plate-forme a des conventions d'appel et des ABI normalisées, et ils le font, et ils définissent généralement la disposition des primitives clés en C (et certains ne définissent pas seulement les conventions d'appel avec les types C, voici un aperçu d'AMD64 SysV) .

Il y a aussi un problème épineux : l'architecture ne définit pas l'ABI, ni le système d'exploitation. Nous devons faire tapis sur un triplet cible spécifique, comme "x86_64-pc-windows-gnu" (à ne pas confondre avec "x86_64-pc-windows-msvc"). Après les tests, il y a un total de 176 triplets.

> rustc --print target-list


aarch64-apple-darwin
aarch64-apple-ios
aarch64-apple-ios-macabi
aarch64-apple-ios-sim
aarch64-apple-tvos


...
armv7-unknown-linux-musleabi
armv7-unknown-linux-musleabihf
armv7-unknown-linux-uclibceabihf
...
x86_64-uwp-windows-gnu
x86_64-uwp-windows-msvc
x86_64-wrs-vxworks
>_

C'est vraiment trop d'ABI car toutes les différentes conventions d'appel comme stdcall vs fastcall ou aapcs vs aapcs-vfp ne sont même pas utilisées dans les tests.

Mais au moins tous ces ABI et conventions d'appel et autres sont disponibles dans un format lisible par machine qui est pratique à utiliser. Au moins, les compilateurs C traditionnels s'accordent sur un ABI pour un triplet cible spécifique ! Bien sûr, il existe des compilateurs C étranges, mais Clang et GCC ne le sont pas :

> abi-checker --tests ui128 --pairs clang_calls_gcc gcc_calls_clang


...


Test ui128::c::clang_calls_gcc::i128_val_in_0_perturbed_small        passed
Test ui128::c::clang_calls_gcc::i128_val_in_1_perturbed_small        passed
Test ui128::c::clang_calls_gcc::i128_val_in_2_perturbed_small        passed
Test ui128::c::clang_calls_gcc::i128_val_in_3_perturbed_small        passed
Test ui128::c::clang_calls_gcc::i128_val_in_0_perturbed_big          failed!
test 57 arg3 field 0 mismatch
caller: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 3A, 3B, 3C, 3D, 3E, 3F]
callee: [38, 39, 3A, 3B, 3C, 3D, 3E, 3F, 40, 41, 42, 43, 44, 45, 46, 47]
Test ui128::c::clang_calls_gcc::i128_val_in_1_perturbed_big          failed!
test 58 arg3 field 0 mismatch
caller: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 3A, 3B, 3C, 3D, 3E, 3F]
callee: [38, 39, 3A, 3B, 3C, 3D, 3E, 3F, 40, 41, 42, 43, 44, 45, 46, 47]


...


392 passed, 60 failed, 0 completely failed, 8 skipped

Ci-dessus, l'abi-checker FFI d'Aria fonctionnant sur Ubuntu 20.04 x64, où elle a testé des situations assez ennuyeuses sur cette plate-forme plutôt importante et bien élevée. Il s'avère que certains arguments entiers n'ont pas réussi à passer par valeur entre deux bibliothèques statiques compilées par Clang et GCC !

_int128Aria a découvert que Clang et GCC ne pouvaient même pas s'entendre sur un ABI sur Linux x64 .

Aria a été conçu à l'origine pour vérifier les erreurs dans rustc, mais je ne m'attendais pas à trouver des incohérences entre les deux principaux compilateurs C sur un ABI important et couramment utilisé.

essayer d'apprivoiser C

Aria pense que ce qui fait peur, c'est que l'analyse sémantique des fichiers d'en-tête C ne peut être effectuée que par le compilateur C de la plate-forme. Même si le compilateur C vous indique les types et comment comprendre les annotations, vous ne connaissez toujours pas la taille/l'alignement/les conventions de tout. Alors, comment interagissez-vous avec tout ce gâchis? Aria propose deux options.

La première option consiste à vous abandonner complètement et à lier votre langage au C, qui peut être l'un des suivants :

  • Écrivez votre compilateur/runtime en C(++) pour qu'il fonctionne en C

  • Faites en sorte que votre "codegen" émette C(++) directement, de sorte que l'utilisateur a de toute façon besoin d'un compilateur C

  • Construisez votre compilateur au-dessus d'un compilateur C majeur à part entière (Clang ou GCC)

Mais tout ce qui précède ne vous mènera pas loin, car à moins que votre langage ne soit vraiment exposé unsigned long long, vous héritez de l'énorme gâchis de portabilité de C.

Ce qui nous amène à la deuxième option : mentir, tricher et voler.

Si tout cela est de toute façon un désastre inévitable, autant commencer à traduire à la main les définitions de type et d'interface dans votre langue, ce que nous faisons tous les jours dans Rust. Par exemple, les gens utilisent rust-bindgen et ses amis pour automatiser les choses, mais souvent les définitions sont vérifiées ou ajustées manuellement. Parce que les gens ne veulent pas perdre de temps à essayer de travailler de manière portable avec le système de construction C personnalisé de Phantomderp.

Dans Rust, qu'y a-t-il sur Linux x64 intmax_t?

pub type intmax_t = i64;

Dans Nim, et sous Linux x64 long long?

clonglong {.importc: "long long", nodecl.} = int64

Une grande partie du code a complètement abandonné le maintien du C dans les boucles et a commencé à coder en dur les définitions des types de base. Après tout, ils ne sont clairement qu'une partie de la plateforme ABI ! Vont-ils changer intmax_tde taille ? Il s'agit clairement d'un changement de rupture ABI !

Alors, sur quoi travaille phantomderp ?

Nous avons expliqué pourquoi intmax_til ne peut pas être modifié, car si nous passons de long long(entier 64 bits) à _int128_t(entier 128 bits), le binaire quelque part devient incontrôlable en utilisant la mauvaise convention d'appel/retour. Mais y a-t-il un moyen, si le code le récupère ou quelque chose du genre, de mettre à niveau les appels de fonction pour la nouvelle application et de laisser l'ancienne application intacte ? Écrivons du code pour tester l'idée que les alias transparents peuvent aider ABI.

Aria a posé sa question : Comment les langages de programmation gèrent-ils ce changement ? Comment spécifier la version avec laquelle intmax_tinteragir ? Si vous avez un fichier d'en-tête C mentionnant intmax_t, quelle définition utilise-t-il ?

Le mécanisme principal pour discuter des plates-formes avec différents ABI ici est le triplet cible. Savez-vous ce qu'est un triplé cible ? Savez-vous ce qui est x86_64-unknown-linux-gnuinclus ? Maintenant, bien qu'il soit ostensiblement possible de compiler contre cette cible et d'obtenir un binaire qui "fonctionne tout simplement" sur toutes ces plates-formes, Aria ne pense pas que certains programmes seront compilés pour être intmax_tplus volumineux que int64_tcela.

Toute plate-forme essayant de faire ce changement deviendra-t-elle un nouveau x86_64-unknown-linux-gnu2 triple cible? Ne serait-ce pas suffisant si quelque chose contre x86_64-unknown-linux-gnula compilation était autorisé à s'exécuter dessus ?

Changer la signature sans casser l'ABI

"Et alors, C ne s'améliorera plus jamais ?" Non ! Mais aussi! Parce qu'ils fournissent une mauvaise conception.

Honnêtement, faire des modifications conformes à l'ABI est une forme d'art. Une partie de cet art est la préparation. Plus précisément, si vous êtes prêt, il est beaucoup plus facile d'apporter des modifications qui ne cassent pas l'ABI.

Comme le souligne l'article de phantomderp, quelque chose comme glibc ( g oui ) l' x86_64-unknown-linux-gnu a gnu compris depuis longtemps et utilise des mécanismes tels que la gestion des versions de symboles pour mettre à jour les signatures et les API, tout en conservant l'ancienne version pour quiconque compile dessus.

Donc, si vous avez int32_t my_rad_symbol(int32_t) , vous dites au compilateur de l'exporter en tant que my_rad_symbol_v1 , alors toute personne qui compile avec cet en-tête l'aura écrit dans son code my_rad_symbol , mais pour la my_rad_symbol_v1 liaison.

Ensuite, lorsque vous décidez qu'il doit être utilisé int64_t, vous pouvez mettre int64_t my_rad_symbol(int64_t) as my_rad_symbol_v2mais conserver l'ancienne définition as   my_rad_symbol_v1. Toute personne compilant avec une version plus récente d'un en-tête utilisera volontiers les symboles v2, tandis que toute personne compilant avec une version plus ancienne continue d'utiliser la v1 !

Mais vous avez toujours un problème de compatibilité : quiconque compile avec les nouveaux en-têtes ne peut pas établir de lien avec l'ancienne version de la bibliothèque, la version V1 de la bibliothèque n'a aucun symbole V2 ! Donc, si vous voulez de nouvelles fonctionnalités, vous devez accepter l'incompatibilité avec les anciens systèmes.

Ce n'est pas un gros problème cependant, cela rend les vendeurs de plates-formes tristes parce que personne n'a un accès immédiat à ce qu'ils ont passé tant de temps à faire. Vous devez déployer une nouvelle fonctionnalité brillante, puis faire en sorte que tout le monde attende qu'elle devienne suffisamment courante et suffisamment mature. Mais pour que les gens soient prêts à s'y fier et à rompre le support des anciennes plates-formes (ou à mettre en œuvre des vérifications dynamiques et des solutions de secours), vous devez attendre quelques années.

Si vous voulez vraiment que les gens mettent à jour tout de suite, parlez de compatibilité ascendante. Cela fait que les anciennes versions des choses fonctionnent d'une manière ou d'une autre avec de nouvelles fonctionnalités dont elles n'avaient aucune idée.

Changer le type sans casser l'ABI

Ainsi, en plus de modifier la signature d'une fonction, pouvez-vous également modifier la disposition des types ? Aria dit que cela dépend de la façon dont vous exposez le type.

L'une des fonctionnalités vraiment merveilleuses de C est qu'il vous permet de distinguer un type avec une disposition connue d'un type avec une disposition inconnue. Si vous déclarez uniquement un type dans un fichier d'en-tête C, tout code utilisateur qui interagit avec lui n'est pas "autorisé" à connaître la disposition de ce type et doit toujours le gérer de manière opaque derrière un pointeur.

Vous pouvez donc créer une API comme MyRadType* make_val()et use_val(MyRadType*), puis utiliser la même astuce de version de symbole pour exposer le symbole make_val_v1AND use_val_v1, et chaque fois que vous souhaitez modifier cette disposition, vous incrémentez simplement la version sur tout ce qui interagit avec ce type. De même, vous en conservez certaines dans MyRadTypeV1et certaines définitions de type pour vous assurer que les utilisateurs utilisent le type "correct". MyRadTypeV2Cela permet de modifier la disposition des types entre les versions.

De mauvaises choses peuvent arriver si plusieurs choses se construisent au-dessus de votre bibliothèque et commencent ensuite à se parler avec des types opaques :

  • lib1 : créer une API, accepter MyRadType*et appeler use_val

  • lib2 : appeler make_valet transmettre le résultat à lib1

Si lib1 et lib2 étaient compilées avec différentes versions de la bibliothèque, cela make_val_v1serait entré use_val_v2! Vous avez deux options pour gérer cela :

1. Dire que c'est interdit, blâmer ceux qui le font quand même et être triste

2. Concevoir d'une manière compatible avec l'avenir MyRadTypeafin que les mélanges soient corrects

Les astuces courantes de compatibilité ascendante incluent :

  • Réserver les champs inutilisés pour les futures versions

  • Toutes les versions de MyRadType ont un préfixe commun qui vous permet de "vérifier" quelle version vous utilisez

  • Avoir des champs auto-dimensionnés pour que les anciennes versions puissent "sauter" de nouvelles sections

ÉTUDE DE CAS : MINIDUMP_HANDLE_DATA

Microsoft est un maître dans ce type de compatibilité ascendante et peut même implémenter la compatibilité de mise en page entre les architectures. Un exemple sur lequel Aria travaille récemment est Minidumpapiset.hMINIDUMP_HANDLE_DATA_STREAM dans .

Cette API décrit une liste versionnée de valeurs. La liste commence par ce type :

typedef struct _MINIDUMP_HANDLE_DATA_STREAM {
    ULONG32 SizeOfHeader;
    ULONG32 SizeOfDescriptor;
    ULONG32 NumberOfDescriptors;
    ULONG32 Reserved;
} MINIDUMP_HANDLE_DATA_STREAM, *PMINIDUMP_HANDLE_DATA_STREAM;

dans:

  • SizeOfHeaderest la taille de MINIDUMP_HANDLE_DATA_STREAM lui-même. S'ils ont besoin d'ajouter plus de champs à la fin, ce n'est pas grave, car les versions plus anciennes peuvent utiliser cette valeur pour détecter la "version" de l'en-tête et également ignorer les champs qu'ils ne connaissent pas.

  • SizeOfDescriptorest la taille de chaque élément du tableau. Cela vous permet de savoir quelle "version" de l'élément vous avez et ignore tous les champs que vous ne connaissez pas.

  • NumberOfDescriptors est la longueur du tableau

  • Reservedest de la mémoire supplémentaire qu'ils ont décidé de conserver dans les en-têtes de toute façon (Minidumpapiset.h fait très attention à ne jamais faire de remplissage n'importe où, car les octets de remplissage ont des valeurs non spécifiées, et c'est un format de fichier binaire sérialisé .. J'espère qu'ils ont ajouté ce champ donc que la taille de la structure est un multiple de 8, il n'y aura donc aucune question de savoir si les éléments du tableau ont besoin d'un remplissage après l'en-tête. C'est prendre la compatibilité au sérieux !)

En fait, Microsoft a une raison d'utiliser ce schéma de version et définit deux versions des éléments de tableau :

typedef struct _MINIDUMP_HANDLE_DESCRIPTOR {
    ULONG64 Handle;
    RVA TypeNameRva;
    RVA ObjectNameRva;
    ULONG32 Attributes;
    ULONG32 GrantedAccess;
    ULONG32 HandleCount;
    ULONG32 PointerCount;
} MINIDUMP_HANDLE_DESCRIPTOR, *PMINIDUMP_HANDLE_DESCRIPTOR;




typedef struct _MINIDUMP_HANDLE_DESCRIPTOR_2 {
    ULONG64 Handle;
    RVA TypeNameRva;
    RVA ObjectNameRva;
    ULONG32 Attributes;
    ULONG32 GrantedAccess;
    ULONG32 HandleCount;
    ULONG32 PointerCount;
    RVA ObjectInfoRva;
    ULONG32 Reserved0;
} MINIDUMP_HANDLE_DESCRIPTOR_2, *PMINIDUMP_HANDLE_DESCRIPTOR_2;




// The latest MINIDUMP_HANDLE_DESCRIPTOR definition.
typedef MINIDUMP_HANDLE_DESCRIPTOR_2 MINIDUMP_HANDLE_DESCRIPTOR_N;
typedef MINIDUMP_HANDLE_DESCRIPTOR_N *PMINIDUMP_HANDLE_DESCRIPTOR_N;

Les détails réels de ces structures ne sont pas très intéressants, sauf :

  • Ils l'ont juste changé en ajoutant des champs à la fin

  • Il existe une définition de type "dernière version"

  • Conservez peut-être encore du rembourrage (RVA est un ULONG32)

C'est un mastodonte indestructible compatible avec l'avant. Ils sont très prudents avec le rembourrage, il a même la même disposition entre 32 bits et 64 bits (ce qui est en fait très important car vous voulez qu'un processeur minidump sur une architecture soit capable de gérer les minidumps de chacun).

Étude de cas : jmp_buf

Aria n'est pas très familière avec cette situation, mais en faisant des recherches sur les pannes historiques de la glibc, elle est tombée sur un excellent article sur LWN : "glibc s390 ABI outages", qu'elle a supposé exact.

Il s'avère que la glibc avait l'habitude de craquer le type ABI, au moins sur s390. Selon la description de cet article, c'est désordonné.

En particulier, ils ont modifié la disposition du type d'état enregistré utilisé par setjmp/longjmp, c'est-à-dire jmp_buf. Maintenant, ils savent qu'il s'agit d'un changement de rupture ABI, ils font donc la chose responsable de la version des symboles.

Mais jmp_bufce n'est pas un type opaque, d'autres choses stockent des instances de ce type en ligne, comme le runtime de Perl. Inutile de dire que ce type relativement obscur a imprégné de nombreux binaires, et le résultat final est que tout dans Debian doit être recompilé !

Ce billet évoque même la possibilité de mettre à jour la version libc pour faire face à cette situation :

Dans un environnement ABI mixte comme debian, les collisions de noms SO entraînent le chargement de deux libcs ​​​​et se disputent le même espace de noms de symboles, tandis que la résolution (et donc le choix ABI) est dictée par les règles d'interpolation et de portée ELF. C'était un vrai cauchemar. Cela pourrait être une pire solution que de dire à tout le monde de reconstruire et de passer à autre chose.

Est-il vraiment possible de changer intmax_t ?

De l'avis d'Aria, pas tout à fait. Tout jmp_bufcomme, ce n'est pas un type opaque, ce qui signifie qu'il est intégré dans un grand nombre de structures aléatoires, est considéré comme ayant un grand nombre d'autres représentations spécifiques au langage et au compilateur, et peut faire partie d'un grand nombre d'interfaces publiques. Et ces interfaces ne sont pas sous le contrôle de la libc, de Linux ou même des mainteneurs de la distribution.

Bien sûr, libc pourrait utiliser l'astuce de version de symbole de manière appropriée pour rendre son API compatible avec la nouvelle définition, mais changer la taille d'un type de données de base comme intmax_tcelui -ci est une quête de confusion dans le vaste écosystème d'une plate-forme.

Aria veut qu'on lui prouve qu'elle a tort, mais pour autant qu'elle puisse le dire, faire un tel changement nécessite un nouveau triplet cible et ne permet à aucun binaire/bibliothèque construit pour l'ancien ABI de fonctionner sur ce nouveau triplet. Bien sûr, quelqu'un pourrait faire le travail, mais Aria n'envie aucune distribution qui le fasse.

Même ainsi, il y a le problème de l'int de x64 : c'est un type très basique, et il a cette taille depuis si longtemps, que d'innombrables applications peuvent avoir des hypothèses étranges et imperceptibles à son sujet. C'est pourquoi int est 32 bits sur x64, même s'il devrait être 64 bits : int était 32 bits depuis si longtemps qu'il était complètement vain de mettre à jour le logiciel à la nouvelle taille, bien qu'il s'agisse d'une toute nouvelle architecture et de triples cibles .

Aria a de nouveau souhaité avoir tort, mais les gens font parfois des erreurs si graves qu'elles sont tout simplement irréversibles. Et si C était un langage de programmation autonome ? Bien sûr, cela peut être fait. Mais ce n'est pas le cas, c'est un protocole, ou un mauvais protocole que nous devons utiliser.

Même si C conquiert le monde, peut-être qu'il n'obtiendra plus jamais de bonnes choses.

Je suppose que tu aimes

Origine blog.csdn.net/weixin_41055260/article/details/123700694
conseillé
Classement