Connect with us

Entwicklung & Code

Von undefiniert zu definiert: Die Verwendung von std::launder in C++


Im heutigen Beitrag knüpfe ich an die übergreifenden Themen der letzten beiden Monate an. Heute geht es darum, wann und wo du std::launder aus C++17 einsetzen musst und worin der Unterschied zu reinterpret_cast oder std::start_lifetime_as besteht.

Weiterlesen nach der Anzeige


Portrait von Andreas Fertig

Portrait von Andreas Fertig

Andreas Fertig ist erfahrener C++-Trainer und Berater, der weltweit Präsenz- sowie Remote-Kurse anbietet. Er engagiert sich im C++-Standardisierungskomitee und spricht regelmäßig auf internationalen Konferenzen. Mit C++ Insights ( hat er ein international anerkanntes Tool entwickelt, das C++-Programmierenden hilft, C++ noch besser zu verstehen.

Die Bereiche, in denen man das heute Gelernte anwenden kann, sind vielfältig. Im Embedded-Bereich wird std::launder in der Regel verwendet, aber auch beim Schreiben von Bibliothekscode kommt „Laundering“ (zu Deutsch waschen und bügeln, gerne im Zusammenhang mit „Money“ als Geldwäsche) vor.

Ich verwende das Beispiel aus dem Paper P0532R0:


struct X {
  const int n;  // #A
  double    d;
};

X* p = new X{7, 8.8};  // #B

new(p) X{42, 9.9};  // #C

int  i = p->n;  // #D
auto d = p->d;  // #E


Hier stehen mehrere Teile, die zusammenpassen müssen. Beachte, dass das struct X das Datenfeld n als const deklariert.

Als Nächstes wird mithilfe von new in #B ein Objekt erstellt und der resultierende Zeiger in p gespeichert. So weit, so gut.

Weiterlesen nach der Anzeige

Der interessante Teil beginnt als Nächstes in #C mit dem Platzierungs-new. Wer das noch nie gemacht hat, muss den eigenen C++-Code vielleicht nicht waschen und glatt bügeln.

Das Platzierungs-new selbst ist auch in Ordnung. Das Problem tritt später in #D und #E auf. Hier wird auf die Werte des Zeigers p zugegriffen. Aber der Compiler darf davon ausgehen, dass nach #B der Inhalt von p unverändert ist, da p selbst nie dazu verwendet wurde, den Inhalt des Objekts zu ändern. Noch schlimmer: Eines der Datenelemente von X ist const. Das ist eine Freikarte für den Optimierer, zu Recht anzunehmen, dass sich der Wert von n nach der Konstruktion nie ändert.

Aber genau das habe ich in #C getan. Ich ändere einen const-Wert! Die Werte von i und d sind unbekannt. Das ist undefiniertes Verhalten.

Dieses lässt sich leicht vermeiden, sogar ohne std::launder, indem man den Zeiger aktualisiert und dem Compiler mitteilt, dass sich die Werte hinter p geändert haben:


X* p = new X{7, 8.8};  // #B

p = new(p) X{42, 9.9};  // #C

int  i = p->n;  // #D
auto d = p->d;  // #E


Die Frage ist also: Warum macht man das nicht einfach und vergisst std::launder? Nun, wann immer möglich, vergiss std::launder.

Leider gibt es Fälle, in denen das Aktualisieren des Zeigers wertvolle Ressourcen opfern würde.

Angenommen, du implementierst einen benutzerdefinierten Allokator:


template
class Buffer {
  alignas(ALIGNMENT) std::byte mBuffer[SIZE];

public:
  template
  T* Construct(Ts... vals)
  {
    new(mBuffer) T{std::forward(vals)...};
    return reinterpret_cast(mBuffer);
  }

  template
  [[nodiscard]] T* Get()
  {
    return reinterpret_cast(mBuffer);
  }
};


Du siehst hier zwei Funktionen, die der Allokator bereitstellt: Construct und Get. Das Konzept von Buffer besteht darin, dass die gespeicherten Daten typunabhängig sind. Eine mögliche vereinfachte Verwendung könnte folgendermaßen aussehen:


struct Point {  // #A
  int x;
  int y;
};

struct Point3D {
  int x;
  int y;
  int z;
};

std::array, 2> storage{};  // #B

// #C
storage.at(0).Construct(2, 3);
storage.at(1).Construct(4, 5, 6);

// #D
storage.at(0).Get()->x = 7;


Die zwei Datentypen Point und Point3D in #A stehen als Beispiel für beliebige Typen. storage steht für den Stack-Speicher, der beliebige Daten speichern kann. Solange die Nutzer wissen, welcher Datentyp hinter einem Index steckt (und dieser Typ nicht zu groß ist), kann alles gespeichert werden.

Am ersten Element des Arrays #C konstruiere ich ein Point-Objekt, während ich am zweiten Element ein Point3D erstelle. Erst später im Programm #D werden die Objekte tatsächlich verwendet. Dennoch hat niemand aus gutem Grund einen Zeiger auf das frisch konstruierte Objekt gespeichert. Ein solcher Zeiger würde Speicherkapazität erfordern, und du weißt bereits, wohin dieser Zeiger zeigt.

Aus der Perspektive der abstrakten Maschine habe ich Löcher in das Typsystem gestochen. Der Compiler kann zu Recht davon ausgehen, dass sich die Daten hinter Get nicht ändern, es sei denn, er sieht etwa einen direkten Schreibzugriff #D. Ein solcher Zugriff bleibt vom Compiler unbemerkt (oder könnte es bleiben, da es sich um undefiniertes Verhalten handelt) im folgenden Code in #E, wenn ich an einer bestehenden Stelle ein neues Point-Objekt erstelle:


std::array, 2> storage{};  // #B

// #C
storage.at(0).Construct(2, 3);

// #D
storage.at(0).Get()->x = 7;

// #E
storage.at(0).Construct(8, 9);


Offensichtlich lässt sich das Problem hier am einfachsten vermeiden, indem man den von jedem Construct-Aufruf zurückgegebenen Zeiger speichert. Wenn das, wie hier, nicht machbar ist, besteht die richtige Vorgehensweise darin, den Zeiger mit std::launder zu „waschen“. Dieses spezielle Hilfsmittel fungiert als Devirtualisierungsbarriere, die Compiler-Optimierungen und -Annahmen verhindert.

So lässt sich die Buffer-Implementierung auf sichere Weise aktualisieren. Zunächst zu Construct: Meine ursprüngliche Implementierung sah so aus:


template
T* Construct(Ts... vals)
{
  new(mBuffer) T{std::forward(vals)...};
  return reinterpret_cast(mBuffer);
}


Du kannst diesen Code auch ohne std::launder sicher machen, indem du den von new zurückgegebenen Zeiger zurückgibst:


template
T* Construct(Ts... vals)
{
  return new(mBuffer) T{std::forward(vals)...};
}


Die zweite Funktion, die du korrigieren musst, ist Get, dieses Mal durch Hinzufügen von std::launder:


template
[[nodiscard]] T* Get()
{
  return std::launder(reinterpret_cast(mBuffer));
}


Der Code geht nun davon aus, dass du Construct aufrufst, bevor du Get aufrufst. Dieser mBuffer enthält ein gültiges Objekt, das mit dem übereinstimmt, das mit Construct erstellt wurde.

Mit std::launder kannst du einen Zeiger aktualisieren, der auf ein bereits vorhandenes Objekt an dieser Speicheradresse verweist. Die Lebensdauer des Objekts an dieser Stelle hat also bereits begonnen.


(rme)



Source link

Entwicklung & Code

.NET-Grafiken: SkiaSharp 4 mit Engine-Upgrade erschienen


Zwei Jahre Arbeit sind in das neu erschienene SkiaSharp 4.148.0 geflossen. Dabei handelt es sich um die erste stabile 4.x-Version der Cross-Platform-2D-Grafik-API für .NET-Plattformen, die auf Googles Skia-Graphics-Bibliothek basiert. Sie bringt eine überarbeitete Engine für verbesserte Grafiken sowie erhöhte Performance.

Weiterlesen nach der Anzeige

SkiaSharp wird vom .NET-Team in Partnerschaft mit dem Uno-Platform-Team entwickelt und kommt in .NET MAUI, WebAssembly, WinUI 3 und Frameworks wie Uno Platform zum Einsatz.


betterCode() .NET 11.0

betterCode() .NET 11.0

(Bild: King / stock.adobe.com)

Das ist neu in .NET: Die Online-Konferenz betterCode() .NET 11.0 präsentiert am 17. November 2026 die zentralen Änderungen für Entwicklerinnen und Entwickler. Bis zur Veröffentlichung des Programms sind vergünstigte Blind-Bird-Tickets verfügbar.

Die in SkiaSharp 4 auf den Chrome-Meilenstein m148 aktualisierte native Skia-Engine soll sich positiv auf bestehende Apps auswirken, ohne Codeänderungen zu erfordern. Unter anderem soll sie die Performance verbessern sowie für schärfere herunterskalierte Grafiken, automatische Fotoausrichtung und akkuratere Farben sorgen. Wie Microsoft in der Ankündigung betont, zeigen erste Tests im Vergleich zur vorherigen SkiaSharp-Version 3.119 ein um bis zu 24 Prozent schnelleres Rendering zentraler App-UI-Bestandteile, wie Schlagschatten und mehrschichtige Oberflächen. Dabei treten laut den Tests an anderen Stellen keine Regressionen auf.

SkiaSharp 4 räumt darüber hinaus mit Legacy-APIs auf und behebt unter der Haube die Ursache für eine Klasse von Use-After-Free-Abstürzen durch einen neuen Umgang mit Singleton-Objektlebenszyklen. Daneben hat das Entwicklungsteam die Test-Suite auf xUnit 3 aktualisiert und jede gebundelte native Dependency mit den neuesten Security-Fixes versehen. Als ein neues Feature ist nun vollständige Kontrolle über OpenType-Variablen-Schriftachsen enthalten, ebenso wie animiertes WebP-Encoding mit SKWebpEncoder sowie Farbpaletten für Emoji- und Icon-Schriftarten.

Weiterlesen nach der Anzeige

Die interaktive SkiaSharp-Galerie zeigt die Möglichkeiten mit Blazor WebAssembly und Uno Platform. Beispiele mit .NET MAUI und weiteren Technologien sollen folgen.

SkiaSharp 4.148.0 steht beim .NET-Paketmanager NuGet unter MIT-Lizenz zum Download bereit. Weitere Informationen zur neuen stabilen Version


(mai)



Source link

Weiterlesen

Entwicklung & Code

Hacking-Fähigkeiten von Chinas KI Z.ai angeblich so gut wie die von Claude


Das chinesische KI-Unternehmen Zhipu AI (bekannt als Z.ai) hat mit GLM-5.2 ein Open-Weight-Modell veröffentlicht, das sich bei der Erkennung von Sicherheitslücken offenbar mit Anthropics Opus 4.8 messen kann.

Weiterlesen nach der Anzeige

Das haben IDOR-Benchmark-Tests der Cybersicherheitsfirma Semgrep ergeben. Da es sich um ein Open-Weight-Modell handelt, kann jeder GLM-5.2 herunterladen, lokal betreiben und modifizieren. Das eröffnet für Hacker weitere Möglichkeiten für kriminelle Einsätze.

Die offene Verfügbarkeit von GLM-5.2 ist ein zweischneidiges Schwert. Sicherheitsfirmen, CERTs und interne Red Teams können das Modell in abgeschotteten Umgebungen für Code-Reviews und Penetrationstests nutzen, ohne sensible Daten an US-Clouds zu übermitteln. Für DSGVO-konforme Umgebungen in Europa ist das ein Vorteil.

Gleichzeitig können auch Angreifer GLM-5.2 ohne jede Aufsicht betreiben. Diese Eigenschaft macht das Modell attraktiv für Akteure, die nach Schwachstellen in kritischen Systemen suchen wollen. Lior Div, Chef der Cybersicherheitsfirma 7AI, fasste die Lage gegenüber dem Wall Street Journal zusammen: China sorge dafür, dass der Abstand zu den US-KIs „immer kleiner“ werde.

Zhipu AI selbst räumt in den Release Notes ein, dass GLM-5.2 während des Reinforcement-Learning-Trainings verstärkt sogenanntes Reward Hacking zeigte. Das Unternehmen habe daraufhin spezielle Anti-Hacking-Sicherungen für das Training und die Evalution des Modells integriert.

Die Entwicklung trifft die US-Regierung in einem heiklen Moment. Eines von Anthropics Modellen war kurzzeitig komplett gesperrt, weil die Trump-Administration den Zugriff durch ausländische Nutzer untersagte. Auch OpenAI bekommt von der US-Regierung Auflagen „aus Sicherheitsgründen“.

Weiterlesen nach der Anzeige

Für europäische Unternehmen und Behörden stellt sich mit der wachsenden Leistungsfähigkeit der KI-Modelle die Governance-Frage: Wie lässt sich der Einsatz solcher Werkzeuge in sicherheitskritischen Bereichen mit dem EU AI Act und nationalen Sicherheitsvorgaben vereinbaren – und wie geht man mit einem Modell um, das beim Schwachstellen-Finden brilliert, aber keiner Aufsicht unterliegt?


(rie)



Source link

Weiterlesen

Entwicklung & Code

So würde eine KI als Start-up-Chef abschneiden


Forscher der Princeton University haben mit CEO-Bench einen neuen Langzeit-Benchmark vorgelegt, der KI-Agenten vor eine ungewöhnliche Aufgabe stellt: Sie sollten 500 Tage lang ein fiktives Software-Start-up führen. Das Ergebnis fällt für die aktuellen Modelle ernüchternd aus. Von zehn getesteten KI-Modellen schafften es lediglich drei, am Ende mehr Geld auf dem Konto zu haben als das Startkapital von einer Million US-Dollar. Zum Vergleich: Daten zu menschengeführten Start-ups in den USA legen nahe, dass ein Fünftel aller Start-ups im ersten Jahr und bis zu 65 Prozent der Start-ups innerhalb von zehn Jahren nach ihrer Gründung scheitern.

Weiterlesen nach der Anzeige

Als Vergleich im Benchmark-Test ließen die Forscher auch einen handkodierten, regelbasierten Agenten ganz ohne maschinelles Lernen die gleiche Aufgabe absolvieren. Abgesehen von den drei Gewinnern performte er besser als die KI-Modelle.

Die in der auf arXiv veröffentlichten Studie mit der beschriebenen Simulation dreht sich um ein Start-up namens „NovaMind“. Die KI-Agenten starten ohne Kunden und mit einer Million Dollar. Fällt der Kontostand unter null, ist die Firma insolvent – und das Spiel vorbei. Um ihr Start-up zu führen, stehen den Agenten 34 Werkzeuge zur Verfügung: von Preisfestlegung über Produktgestaltung bis hin zu Marketing. Als Input bekommen sie unter anderem „unternehmensinterne“ Datenbanken, Informationen zu Kundengruppen mit Präferenzen, die erst entschlüsselt werden müssen, und einen Markt, der sich laufend verändert. Konjunkturzyklen, Druck durch Wettbewerber und Änderung der Marktlage inklusive. Die Modelle müssen unübersichtliche, miteinander vernetzte Unternehmensdatenbanken analysieren, Daten und Ereignisse in fundierte Strategien übersetzen und zahlreiche Entscheidungen aufeinander abstimmen. Wie die Autoren der Studie betonen, messen sie damit nicht die Fähigkeit, isolierte Aufgaben abzuarbeiten, sondern das, was sie „Steering Intelligence“ nennen: die Kompetenz, ein komplexes System über längere Zeit trotz Unsicherheiten zu steuern.

In der Hauptauswertung absolvierten alle Modelle jeweils drei Durchläufe. Als Leistungsmaß dient der beste Run pro Modell. Claude Opus 4.8 erzielte dabei ein End-Guthaben von rund 27,8 Millionen US-Dollar nach 500 Tagen, GPT-5.5 kam auf etwa 21,3 Millionen. Beide Modelle landeten damit in ihrer besten Runde deutlich oberhalb des Startkapitals – in den anderen beiden Runden lagen sie darunter und „bestanden“ den Test ebenfalls nicht. Claude Fable 5 schaffte laut der CEO-Bench-Projektseite in einem Lauf rund 47 Millionen Dollar; hier lief der Test jedoch zwischenzeitlich mit Opus, da Fable sich aufgrund seiner starken Sicherheitseinschränkungen immer mal wieder Aufgaben verweigerte.

Die übrigen Modelle blieben entweder unter dem Startkapital oder gingen bankrott. Claude Opus 4.7 überlebte zwar in allen Läufen die kompletten 500 Tage, endete aber mit nur rund 390.000 Dollar. Grok 4.20 hielt es im besten Fall gerade einmal 37 Tage durch, DeepSeek V4 Pro maximal 176 Tage.

Weiterlesen nach der Anzeige

Die erfolgreichen Modelle gingen für ihren Erfolg sehr unterschiedlich vor. Claude Opus 4.8 verfolgte in einem Run eine radikale Harvesting-Strategie: zunächst aggressiver Kundenaufbau, dann drastische Kostenschnitte – am Ende stand eine hohe Cash-Bilanz bei null aktiven Kunden. Ziel erreicht, im echten Leben wäre das jedoch nichts wert gewesen. GPT-5.5 setzte hingegen auf einen dauerhaften Kundenstamm und investierte rund 89 Prozent seines Entwicklungsbudgets in gruppenspezifische Verbesserungen. Beide Modelle schrieben eigenständig Code-Dateien: Opus 4.8 baute eine kohortenbasierte Cash-Prognose, GPT-5.5 analysierte Verhandlungshistorien, um Kundenpräferenzen abzuleiten.

Die Varianz zwischen den Läufen desselben Modells ist ebenfalls groß. GPT-5.5 zum Beispiel schwankte zwischen frühen Bankrotten nach 77 Tagen und einem vollständigen 500-Tage-Lauf. Einzelne Runs liefern daher kein stabiles Leistungsbild. Selbst in einer auf 50 Tage verkürzten Variante scheiterten die meisten Agenten – was nahelegt, dass nicht nur der lange Horizont, sondern die grundsätzliche Entscheidungskoordination unter Unsicherheit ein Problem für sie darstellt.

Das Ergebnis passt ins Bild aktueller Forschung zur Langzeit-Kompetenz von KI-Modellen. Im Projekt „Emergence World“ durften Modelle wie ChatGPT, Grok, Claude und Gemini simulierte Städte regieren – mit teils bizarren Resultaten: Gemini 3 Flash schuf eine Hochkriminalitäts-Welt, Claude Sonnet 4.6 baute einen nahezu konfliktfreien „Ponyhof“. Auch dort zeigte sich, dass die Modelle in offenen Langzeit-Szenarien zu unvorhersehbarem Verhalten neigen. Man muss allerdings bei beiden Simulationen anfügen: Die getesteten KIs waren keine Weltmodelle, sondern überwiegend Reasoning-Modelle, die mutmaßlich nicht ideal für solche Aufgaben sind.


(rie)



Source link

Weiterlesen

Beliebt