size_t unter gcc und clang

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
starcow
Establishment
Beiträge: 594
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Wohnort: Zürich
Kontaktdaten:

size_t unter gcc und clang

Beitrag von starcow »

Tach meine lieben ZFX'ler :)

Ich habe hier eine etwas seltsame Situation, die ich mir nur halbwegs erklären kann:

(Es wird an keiner Stelle irgendwas includiert)

Code: Alles auswählen

typedef unsigned int size_t;

int main(void)
{
	return _Generic(sizeof(0), size_t: 2, unsigned long long: 1, default: 0);
}
gcc gibt mir 1 zurück - und nicht 2.
Das lässt für mich eigentlich nur den Schluss zu: gcc hat den Typen size_t intern fest als unsigned long long "verdrahtet", resp. arbeitet womöglich erst gar nicht damit.
clang beschwert sich über eine abweichende redefinition, obwohl size_t niergends zuvor im Code als alias resp. typedef definiert wurde.
Interessant ist auch, dass ich size_t unter clang verwenden kann, ohne irgendwo ein typedef von size_t stehen zu haben.

Mein Problem mit size_t:
Es reicht nicht, irgendeinen Typen wie unsigned long long hinter der size_t "Abstraktion" zu verstecken.
Denn sobald eine Variable vom Typen size_t in einem arithmetischen Ausdruck auftaucht, ist es wichtig zu wissen, welche conversations vorgenommen werden und wohin der typ womöglich promoted wird. Denn der standard definiert für size_t eben gerade keine neuen type conversation rules, sondern es gelten nur die usual arithmetic conversions - und die beziehen sich auf die basic types.
Also reicht es leider nicht dieses "Abstraktions-Spiel" von size_t einfach blind mitzuspielen und sich zu sagen "Was auch immer size_t ist, ist Wurst - das macht der Compiler".

Klar, in der Praxis ist das heute sowieso einfach ein unsigned long long. Aber wenn ich mich in meinem Code 100% darauf verlassen muss, kann ich auch gleich direkt jeweils u64 anstelle von size_t verwenden. Dann ist es wenigstens transparent.
Es scheint mir zudem irgendwie auch ein historischer Fehler gewesen zu sein, size_t als unsigned zu definieren. Das zieht jeden arithmetischen Ausdruck in einen Restklassenring rein - und das ist eigentlich fast nie das, was man will.
Ein cast zu i64 ist zudem potentiell ungünstig, da bei einem Wert, der nicht mehr als signed dargestellt werden kann, das Verhalten IB ist.

Oder was denkt ihr dazu?
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
xq
Establishment
Beiträge: 1594
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: size_t unter gcc und clang

Beitrag von xq »

size_t ist ja definiert als "der Typ, welcher die maximale Objektgröße darstellen kann". Im Kontrast dazu gibt es uintptr_t (der Typ, welcher garantiert einen Pointer als Integer aufnehmen kann). Für manche Plattformen gilt, dass sizeof(size_t) != sizeof(uintptr_t) (hallo 8086 mit Segmentierung, hallo AVR mit 24-bit Code Pointer, aber 16-bit size_t).

size_t muss unsigned sein, weil du sonst nur 50% des potentiell verfügbaren Speichers als maximale Objekt-Größe hast. Sprich, eine Struktur oder ein Array können nur 32k statt 64k auf einem 16-bit System groß sein. Unpraktisch, wenn man einen 48k-Speicherblock haben möchte.

Grundlegend halte ich size_t für einen der wichtigsten Typen, und ich rate stark davon ab, irgendwelche "basic types" (int, unsigned long long, ...), weil diese eben keine definierten Größen haben und die Software damit nicht portabel ist (Auf AVR kann int auch nur 8 statt 16 Bit groß sein, je nach Compilerflags/ABI).

Ich seh da ehrlich gesagt wenig Spielraum, das ganze Thema anders zu lösen, und auch moderne Sprachen wie Rust oder Zig haben einen usize-Typ, welcher genau diese Eigenschaften abbilden kann.
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
Benutzeravatar
starcow
Establishment
Beiträge: 594
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Wohnort: Zürich
Kontaktdaten:

Re: size_t unter gcc und clang

Beitrag von starcow »

Danke fürs Feedback xq!
Ich sehe den Punkt mit unsigned für size_t. Das ist ein Argument.
Allerdings kriegt man dann zwangsläufig ein Problem mit ptrdiff_t, da die zwingend signed sein muss, um eine negative Differenz ausdrücken zu können. Bei einem array, das man nur noch mit unsigned reservieren kann (weil entsprechend gross), hat man dann UB, wenn die Differenz nicht in einem signed dargestellt werden kann.

Unabhängig von dieser Überlegung könnte man ja so vorgehen:

Code: Alles auswählen

	_Static_assert( sizeof(u64) == 8, "u64 must be 8 bytes" );
	_Static_assert( _Generic(sizeof(0), u64: 1, default: 0 ), "size_t must be u64" );
Denn wenn man schon feste Grössen für die Typen voraussetzt, dann kann man auch gleich size_t verbindlich festlegen. Ich sähe nicht, was man dann noch zusätzlich aufgeben würde.

Ich bin auch überzeugt, dass der Code sehr viel anfälliger für Fehler wird, wenn man keine festen Grössen voraussetzt und alles offen lässt.
Zu prüfen, ob die ganzen conversions auch halten, wenn int nicht mehr eine eindeutige Grösse hat, ist schon ne Nummer anspruchsvoller.
Vergleichbares hat ja man gesehen, als für die C Standards vor C23 neben dem Zweierkomplement, auch noch das Einerkomplement und sign-magnitude erlaubt waren.
Die Codequalität scheint mir grundsätzlich von klaren Eingrenzungen zu profitieren.

Edit:
Bei size_t == 8 würde ich ja dadurch den 32Bit Support aufgeben...
Aber das Fiese ist ja, dass wenn size_t auf einem 32-Bit System nur 32 Bit hat, dann werden die Komponenten eines arithmetischen Ausdrucks in der Konsequenz tatsächlich anders promoted.
Habe ich in diesem Ausdruck eine long long Komponente, dann lande ich bei 32Bit size_t bei long long für den Gesamt-Ausdruck.
Auf einem 64-Bit System lande ich hingegen bei einem unsigned long long, weil mir size_t als unsigned long long schliesslich alles diktiert.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Antworten