1 Punkte von GN⁺ 3 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • In Linux 7.2 verschwinden die Einsatzstellen der internen strncpy-API im Kernel, womit die seit Langem zur Abschaffung vorgesehene String-Kopier-Schnittstelle endgültig entfernt wird
  • strncpy() kopiert zwar eine vorgegebene Anzahl an Bytes, doch das Verhalten bei der NUL-Terminierung ist nicht intuitiv, weshalb die Funktion im Kernel über Jahre eine Fehlerquelle blieb
  • Die Eigenschaft, den Zielpuffer unnötig mit Nullen aufzufüllen, verursachte zudem Performance-Probleme; um das zu beseitigen, waren rund 6 Jahre und 362 Commits nötig
  • Im Merge vom Freitag wurde nicht nur die API selbst entfernt, sondern auch die letzte architekturspezifische per-CPU-Implementierung
  • Kernel-Code muss nun je nach Einsatzzweck zwischen Ersatzfunktionen wie strscpy(), strscpy_pad(), strtomem_pad(), memcpy_and_pad() und memcpy() wählen

strncpy verschwindet in Linux 7.2

  • Linux 7.2 entfernt die seit Langem zur Abschaffung vorgesehene strncpy-API endgültig aus dem Kernel
  • Nach 6 Jahren Aufräumarbeiten bleibt im Kernel-internen Code keine Nutzung der strncpy-Schnittstelle mehr übrig
  • Diese Änderung ist mehr als ein bloßer Funktionstausch; sie kommt einer kernelweiten Bereinigung veralteter Praktiken beim Kopieren von Strings gleich

Umfang der Arbeiten bis zur Entfernung

  • Für die Entfernung von strncpy waren etwa 362 Commits nötig
  • Die Arbeiten verliefen schrittweise, indem Nutzungen von strncpy im Kernel nach und nach beseitigt wurden
  • Mit Linux 7.2 erreicht diese Bereinigung nun ihren Abschluss

Warum strncpy im Kernel problematisch war

  • strncpy galt im Linux-Kernel über Jahre als anhaltende Fehlerquelle
  • Besonders zwei Verhaltensweisen waren problematisch
    • Bedeutung und Verhalten der NUL-Terminierung sind nicht intuitiv, wodurch leicht Fehler entstehen
    • Das redundante Auffüllen des Zielpuffers mit Nullen verursacht unnötige Performance-Kosten

Der tatsächliche Entfernen-Merge

  • Der am Freitag erfolgte Merge entfernt die strncpy-API
  • Im selben Merge verschwindet auch die letzte architekturspezifische per-CPU-strncpy-Implementierung

Ersatz-APIs für Kernel-Code

  • Anstelle von strncpy muss nun je nach Ziel und Abschlussbedingung die passende Funktion gewählt werden
    • strscpy(): für NUL-terminierte Ziele
    • strscpy_pad(): für NUL-terminierte Ziele, wenn Null-Padding erforderlich ist
    • strtomem_pad(): für nicht NUL-terminierte Felder mit fester Breite
    • memcpy_and_pad(): für begrenzte Kopien mit explizitem Padding
    • memcpy(): für Speicherkopien mit bekannter Länge

1 Kommentare

 
GN⁺ 3 시간 전
Hacker-News-Kommentare
  • Früher hat man sich gern darüber lustig gemacht, dass die Linux-Kernel-Entwickler, angeblich C-Entwickler auf Weltniveau, keine stringbuffer- oder stringview-Typen bauen konnten. Damals gab es zu diesem Thema aber noch keinen Konsens wie heute, also ist das bis zu einem gewissen Grad verständlich.
    Dennis Ritchie war jemand, der die richtige Richtung schon gesehen hatte, und schlug 1990 einen Fat-Pointer-Typ für C vor. Wäre das in C99 aufgenommen worden, wäre es eine perfekte Ergänzung gewesen; hätte das Komitee es übernommen, hätte die Welt durchaus anders aussehen können.
    2007 gab es mit Walter Brights Text „C's greatest mistake“ eine zweite Chance, in dem im Wesentlichen dieselbe Idee wie bei Ritchie, nämlich Slices/Stringviews, klarer erklärt wurde, aber auch das schaffte es nicht in C11. Jetzt sind wir bei C23, und immer noch gibt es das nicht; stattdessen haben wir _Generic und VLA bekommen, also wirkt es fast so, als solle man einfach fröhlich weiterfeiern.

    • Walter Brights Text von 2007 ist hier: https://digitalmars.com/articles/C-biggest-mistake.html
      Beim Suchen bin ich auch auf einen Reddit-Post zum selben Thema gestoßen, und die Fahrradschuppen-Debatte war ziemlich lustig: https://www.reddit.com/r/C_Programming/comments/90uq7c/cs_bi...
      Ich frage mich, warum das Verhalten, dass C-Arrays zu Zeigern zerfallen, überhaupt so entworfen wurde. Es gibt die Erklärung, dass man damit B-Code mit minimalen Änderungen nach C kompilieren wollte; in B definierte eine Array-Deklaration offenbar tatsächlich sowohl einen Zeiger als auch ein Array, und dieser Zeiger wurde so initialisiert, dass er auf das erste Element des Arrays zeigte.
    • VLA wurde in C11 zu einem optionalen Feature herabgestuft, und das halte ich für eine gute Sache.
      Das größere Problem heute ist, dass die C-Standardbibliothek immer noch in der K&R-Ära feststeckt und nicht einmal Sprachmerkmale wie Strukturargumente oder Rückgabewerte, die schon in C99 hinzugekommen sind, in ihren APIs widerspiegelt. Schon allein Bereichsstrukturen als Zeiger-/Größen-Paare in der Standardbibliothek plus neue oder modernisierte String-Funktionen, die diese verwenden, würden die Lage deutlich verbessern.
    • Link zu Ritchies Vorschlag: https://web.archive.org/web/20150611114358/https://www.bell-...
    • Das ist das Muster, das mich in Teamarbeit am meisten stört. Es gibt Lösung A, B und C, jede mit Vor- und Nachteilen, man diskutiert zwei Wochen lang darüber, und am Ende wird gar nichts ausgewählt.
    • Das zeigt nur, wo die Prioritäten der WG14 liegen.
  • strncpy im Linux-Kernel sei jahrelang eine „hartnäckige Bugquelle“ gewesen – wegen kontraintuitiver Semantik, der Behandlung der NUL-Terminierung und der Performance-Probleme durch unnötiges Auffüllen des Ziels mit Nullen.
    Jedes Mal, wenn ich um ein Review von C-Code gebeten wurde, habe ich nach strncpy gesucht, und dort habe ich immer Bugs gefunden.

  • Es gibt Dinge, die mich seit 40 Jahren nerven. NUL-terminierte Strings, und inzwischen auch Nicht-UTF-8-Strings bei der Ein- und Ausgabe.
    Dasselbe gilt für die Konvention, Zeilenenden als LF, CR oder CRLF zu behandeln, und für die Trennung von Feldern durch Pipes oder Kommas. Hätte man eindeutige ASCII-Zeichen wie GS, FS oder RS verwendet, wäre die Kodierung und Dekodierung von Zeilenenden ein Problem der Ein-/Ausgabe geworden, und HT/VT/CR/LF/FF hätten ganz wörtlich Code für die Ausgabe bleiben können.

    • Ich habe einmal an einem Projekt gearbeitet, das Daten konvertierte, die mit ASCII-Feld-/Datensatz-Trennzeichen gerahmt waren, und das ließ sich wirklich sehr leicht verarbeiten.
      Das ganze unschöne Escaping von komma-separierten Daten fiel weg, wodurch alles viel einfacher wurde.
    • In Unicode gibt es inzwischen noch mehr Optionen. Es gibt NL Next line, das wie ein Mitbringsel aus EBCDIC wirkt, dazu den von Unicode eingeführten LS Line separator und PS Paragraph separator.
      Der Unicode-Standard sagt, dass nicht nur CR, LF, CRLF und diese Zeichen, sondern auch Vertical Tab und Form Feed als Zeilentrenner behandelt werden sollen.
    • UTF-8 funktioniert für Standardein- und -ausgabe vollkommen problemlos. Natürlich gilt das nur, sofern man nicht von Windows spricht, das bei internationaler Textkodierung offenbar in den frühen 90ern stehen geblieben ist.
      Zeilenenden wie LF, CR und CRLF sind auch eine Betriebssystemkonvention, und es ist besser, wenn Programmiersprachen nicht versuchen, das „richtige“ Zeilenende zu erraten. Das schafft mehr Probleme, als es löst, und wieder einmal ist es größtenteils ein Windows-spezifisches Problem, bei dem Microsoft Windows endlich ins aktuelle Jahrhundert holen müsste.
    • LF ergibt am meisten Sinn, aber bei Textdateien ist beides in Ordnung. Das Problem ist, dass CSV kein Text ist.
      Als ich zuletzt in bash mit CSV-Dateien arbeiten musste, habe ich sie intern zur Verarbeitung in RS und FS umgewandelt.
    • Meiner Meinung nach sollte man einfach überall UTF-8 verwenden.
  • Statt strncpy solle man im Linux-Kernel-Code strscpy() für NUL-terminierte Ziele, strscpy_pad() für NUL-terminierte Ziele mit benötigtem Null-Padding, strtomem_pad() für nicht NUL-terminierte Felder fester Breite, memcpy_and_pad() für grenzbasierte Kopien mit explizitem Padding und memcpy() für Speicher-Kopien mit bekannter Länge verwenden.
    Das klingt wie ein Albtraum, und ich weiß nicht, ob es wirklich so kompliziert sein muss.

    • Der Grund ist Performance. Eine sichere Universal-Funktion, die das meiste davon abdeckt, wäre wegen interner Verzweigungen zwangsläufig langsamer, und in der Wahl der Funktion steckt auch die Absicht des Entwicklers.
      Ich finde es besser, wenn beim Lesen des Codes allein durch die gewählte Funktion klar wird, was beabsichtigt ist.
    • strncpy korrekt zu verwenden war schon immer kompliziert.
    • Wenigstens hätte man ihnen vielleicht etwas bessere Namen geben können.
  • Genau in solcher langwierigen Fleißarbeit findet die eigentliche Arbeit des Systems Engineerings statt.
    Solche großen Infrastrukturprojekte, die Linux über den gesamten Prozess hinweg praktisch nutzbar halten und gleichzeitig verlässlicher machen, laufen nicht über Monate, sondern über Jahrzehnte.

    • Warum es Jahrzehnte dauert, kann ich nachvollziehen. Der lange Schweif aus Nutzern und Abhängigkeiten ist wirklich lang.
      Aber ich bin mir nicht sicher, ob man in diesem Tempo langfristig wirklich bedeutenden Fortschritt erzielen kann. Das ist weniger eine Beschwerde als vielmehr ein Paradox kritischer Infrastruktur.
  • Eine großartige Arbeit, die einen auch demütig macht. Erstaunlich, dass so viele Menschen dazu beigetragen haben
    „Tolle neue Features“ bekommen leicht Anerkennung, aber bei etwas so Grundlegendem wie dem Kernel kann es sogar wichtiger sein, schlechte Funktionen zu entfernen
    Wenn in 50 Jahren die Menschen vergessen haben, wie man Quellcode liest, während sich Claude-/Codex-Abfälle still auftürmen und den Großteil der Energie der Erde verbrennen, werden solche Arbeiten wohl wie Legenden aus dem „Gründungszeitalter“ wirken

    • Musste dabei an Vernor Vinges A Deepness in the Sky denken. Darin wartet jemand ein Raumschiff mittels Software-Archäologie
      und ist außerdem die einzige Person, die weiß, was die Unix-Epoche ist
    • Ich glaube nicht, dass in 50 Jahren alle vergessen haben werden, wie man Quellcode versteht. Der menschliche Drang, wissen zu wollen, wie Dinge funktionieren, wird auch dann noch da sein
    • Von KI erzeugter Mischmasch-Code wird meiner Meinung nach schon viel früher unbeherrschbar werden
  • Ich halte nullterminierte Strings für den größten Fehler der Computergeschichte. Pascal-Strings waren viel sicherer

    • Es gibt auch Zwischenlösungen wie BSTR, wie sie Visual Basic und später COM gewählt haben
      Es ist immer noch ein Pointer auf ein nullterminiertes Zeichenarray, aber direkt vor dem ersten Byte, auf das der Pointer zeigt, steht ein Längenfeld. Unter der Annahme, dass keine eingebetteten NUL-Zeichen vorkommen, ist das auch mit C-Strings kompatibel, und Funktionen für den Typ BSTR können den Längenwert nutzen
    • Stimme teilweise zu, aber vermutlich hätte es Streit über den Datentyp des Größenfelds gegeben. Wenn es nicht variabel lang gewesen wäre, erst recht, und bei variabler Länge hätte es wieder andere Probleme gegeben
      Eine Zeit lang hätten sich vielleicht sogar 16 Bit als übertrieben angefühlt, und heute könnten 32 Bit zu klein wirken. C gilt als Sprache mit „starker Typisierung“, ist aber ausgerechnet an wichtigen Stellen ziemlich lax
    • Nullterminierte Strings waren die Grundlage für enorm viel nützliche Software. Das den größten Fehler der Computergeschichte zu nennen, ist etwas übertrieben
      Ich habe seit über 30 Jahren keinen Pascal-bezogenen Code mehr geschrieben, aber ich erinnere mich dunkel, dass ich selbst damals das String-System für zu umständlich hielt
    • Sollten 255 Zeichen nicht für alle reichen?
    • Fast so schlimm wie zeilenumbruchterminierte Zeilen
  • Es gibt so viel Schmerz und vergeudete Mühe nur deshalb, weil es keinen String-Datentyp gibt

    • Genauer gesagt liegt der Schmerz und die vergeudete Mühe nicht daran, dass es keinen String-Datentyp gibt, sondern daran, dass man um den Umstand herumarbeiten muss, dass C keinen String-Datentyp hat
    • Wie könnte man hier überhaupt starke Typisierung einführen? Dafür müsste man doch wohl auch den Code rund um strncpy großflächig refaktorieren, damit er diesen Typ und die zugehörigen Funktionen verwendet
  • Ich frage mich, was daran so schwierig war, die Verwendungen von strncpy umzuschreiben, dass es 6 Jahre gedauert hat
    Ob es einfach so weit verbreitet war, ob man es nur im Rahmen einer langfristigen Arbeit ersetzt hat, wenn dieselben Dateien ohnehin angefasst wurden, oder ob es noch andere Schwierigkeiten gab, würde mich interessieren

  • Ich musste einmal Code in einer Win32-App betreuen, der mit Leerzeichen aufgefüllte Strings verwendete. Der Ziel-String wurde mit Leerzeichen aufgefüllt, aber im letzten Byte stand trotzdem noch ein Nullzeichen
    Für Dinge wie Länge und Kopieren musste man spezielle Versionen der String-Funktionen verwenden. Warum das so war, weiß ich nicht, aber die Codebasis war so alt, dass es auch aus dem Verhalten von Pascal-Structs stammen könnte

    • Das könnte daran gelegen haben, dass die Strings aus einem char-Feld einer SQL-Datenbank kamen. Anders als varchar werden char-Felder mit Leerzeichen aufgefüllt
    • Ich vermute, die Wurzeln dieses Verhaltens liegen eher in COBOL als in Pascal
    • Es könnte auch darum gegangen sein, Reallokationen zu vermeiden, wenn sich die String-Größe ändert, oder um die Ausrichtung auf CPU-Cache-Lines