C ist keine Low-Level-Sprache: Ihr Computer ist kein schneller PDP-11
C Is Not a Low-Level Language (2018)
Die Meltdown- und Spectre-Sicherheitslücken haben ihre Wurzeln in dem Versuch, Prozessoren zu bauen, die C-Code schnell ausführen. Moderne CPUs sind keine simplen PDP-11-Emulatoren mehr, sondern hochkomplexe Maschinen mit Out-of-Order-Ausführung und Spekulation. C-Compiler müssen enorme Anstrengungen unternehmen, um die Illusion einer einfachen, hardwarenahen Sprache aufrechtzuerhalten. Dieser Artikel zeigt, warum C längst keine Low-Level-Sprache mehr ist und wie die Abstraktionen der Sprache die Leistung und Sicherheit beeinträchtigen.
Die Funktionen, die zu diesen Schwachstellen führten, wurden hinzugefügt, damit C-Programmierer weiterhin glauben konnten, sie programmierten in einer Low-Level-Sprache, obwohl das seit Jahrzehnten nicht mehr der Fall ist.
- weitendorf
Das ist meiner Meinung nach ein so pedantischer Punkt. C ist eine Low-Level-Sprache, weil es sehr einfach macht, mit Maschinensprache/Assembler zu arbeiten und Dinge wie dieses zu tun (LLM-gestütztes Beispiel folgt):
int main() {
__m512i vecA = _mm512_setr_epi32(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
__m512i vecB = _mm512_setr_epi32(0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75);
unsigned short mask = 0;
__asm__ (
"vp2intersectd %[B], %[A], %%k2"
: "=@cck2" (mask)
: [A] "v" (vecA), [B] "v" (vecB)
: "k3"
);
printf("Intersection Mask: 0x%04X\n", mask);
return 0;
}
Das ist etwas, das "Low-Level"-Programmierer sehr oft nutzen, um die Vorteile einer Hochsprache zu realisieren und gleichzeitig explizite Kontrolle über die Verwendung bestimmter Hardware-Instruktionen auszuüben (vp2intersectd ist eine AVX-512-Instruktion, die in hochoptimierten Suchalgorithmus-Implementierungen verwendet wird).
Offensichtlich ist man nicht mehr "Low-Level", wenn man sich auf implizites Verhalten des Compilers zur Optimierung des Codes verlässt. Aber wenn man schnell und einfach auf Maschinenebene-Instruktionen zurückgreifen kann, um explizite Implementierungssemantik zu liefern, und die Sprache das tatsächlich relativ einfach und leicht macht, dann scheint mir das durchaus "Low-Level" zu sein.
- bee_rider
"Low-Level-Sprache" ist einer dieser Begriffe wie "VLSI" (Very Large Scale Integration), die in den 70ern oder so definiert wurden, sodass die akademische Definition nicht mehr mit dem übereinstimmt, was die meisten Leute erwarten würden.
Das ist in Ordnung, es ist ein Fachbegriff, und solche müssen nicht unmittelbar einleuchtend sein.
Ich mag den Titel dieses Artikels aus diesem Grund jedoch nicht. Wirklich besser wäre ein Titel wie "Ein moderner x86-Prozessor ist keine PDP-11". Der Untertitel ist im Grunde perfekt.
Edit: Außerdem ist es meiner Meinung nach nicht wirklich fair, C dafür zu verprügeln; das Problem ist nicht wirklich eines der Low-Level-Ness. Eine Sprache, die tatsächlich die Komplexität von spekulativer Ausführung und all dem offenlegen würde, könnte ziemlich hochsprachlich sein. Es wäre nur schwieriger, sie in einem linearen Texteditor zu lesen, oder? Wir wären besser dran, den Abhängigkeitsgraphen zu zeichnen oder so.
- legobmw99
Ich bin seit Jahren ein Fan dieses Artikels, obwohl er mich oft dazu bringt, nachzudenken, dass es wirklich keine echten Low-Level-Sprachen für unsere superskalaren modernen CPUs gibt. Weiß jemand von irgendwelchen?
- melodyogonna
Nun, C gibt dir die Kontrolle, wenn es darauf ankommt, auch wenn sie sich für modernere Funktionen eher angeklebt anfühlen.
Ich glaube tatsächlich, dass Mojo die einzige moderne Sprache ist, die nicht so tut, als ob jeder Computer eine PDP-11 wäre. C war so erfolgreich, dass viele nachfolgende Sprachen einfach C-Dinge als Selbstverständlichkeit übernommen haben.
In Mojo ist alles mit der Komplexität des modernen Computers im Hinterkopf entworfen, und in jeder Phase hat der Programmierer vollständige Kontrolle über die Ergebnisse. Du entscheidest, was inline gesetzt wird, was in Registern übergeben wird, was entrollt wird usw. Die Sprache hat ausgezeichnete ... nein, wahrscheinlich die beste portable SIMD-Unterstützung, die es gibt; alle Integer sind auf SIMD aufgebaut, und die skalaren Integer sind nur SIMD mit Länge 1. Du hast auch vollständige Kontrolle darüber, was kompiliert wird, dank leistungsfähiger Compilezeit-Programmierung, die ähnlich, aber leistungsfähiger als die von Zig ist (meiner Meinung nach, weil du viel mehr Informationen liefern kannst). Während C und Rust Inline-Assembler erlauben, geht Mojo weiter, indem es dir erlaubt, Inline-MLIR und LLVM bereitzustellen, sodass du in Situationen, die es rechtfertigen, dem Compiler sagen kannst, eine bestimmte LLVM-Intrinsic zu kompilieren. Die Sprache geht auch nicht davon aus, dass du für nur eine Maschine kompilierst; jeder moderne Computer ist von Natur aus heterogen und kann mehrere programmierbare Einheiten enthalten, daher ist die Compiler-Pipeline so ausgelegt, dass sie das Kompilieren bestimmter Teile des Codes für ein Ziel und anderer Teile für andere Ziele ... als eine Kompilationseinheit erlaubt.
- glouwbug
Vielleicht nicht damals, aber wir haben jetzt im Grunde unser eigenes Arm-Leute-Template-System:
#define array(T, N) struct array##T##N { T value[N]; }
void copy(array(int, 32)* x, array(int, 32)* y) {
*x = *y;
}
int main() {
array(int, 32) x;
array(int, 32) y = { 1, 2, 3, 4 };
copy(&x, &y);
}
Mit (Gerüchten über) Lambdas und defer auf dem Weg geht C den Weg des klassischen WoW.