Früher war alles einfach. Zu welchem Netz eine IP-Adresse gehörte konnte allein anhand der Adresse festgemacht werden. Das Konzept nannte sich Netzklassen und wurde bereits nach wenigen Jahren wieder verworfen, weil es nicht die benötigte Flexibilität bot.

Heutzutage werden Netze in der Regel als Subnetze angegeben. Dabei erfolgt die Angabe einer Basisadresse und einer Netzmaske. Die Netzmaske wird dabei für ein binäres AND verwendet. Jede Adresse, welche nach einem binären AND mit der Netzmaske auf die selbe Basisadresse zeigt, gehört zum selben Netz. Eigentlich recht simpel:

In C++ geht das nun noch einfacher. Da eine IP (und folglich auch die Netzmaske) aus 32 Bit bestehen kann man die IP einfach als Integer betrachten und ist nach einer Operation fertig. Soweit kein Problem.
Nun hat man festgestellt, dass es doch ein wenig aufwändig ist, wenn man für die Netzmaske quasi jedes Mal eine zweite IP aufschreiben muss. Mit der Einführung von Classless Inter-Domain Routing (CIDR) hat man daher eien neue Schreibweise eingeführt: die Slashnotation.
Beispiel: 10.0.0.0/8 gibt an, dass das Netz bei der Basisadresse 10.0.0.0 beginnt und die ersten 8 Bit der Netzmaske gesetzt sind. Dies entspricht also dem Netz 10.0.0.0 / 255.0.0.0.0.
Womit wir beim eigentlichen Thema des Postings angekommen wären: Der Umwandlung von Slashnotation zur eigentlichen Netzmaske. In einem älteren Projekt habe ich dazu folgenden Ansatz gewählt:
Netzmaske = (1 << Bitzahl) - 1
Der Gedanke dahinter: Wenn man eine Eins (also das niedrigste Bit) um n Stellen nach links schiebt, kommt eine Eins mit n Nullen heraus. Wenn man nun 1 subtrahiert wird im Binärsystem die Eins durch eine Null ersetzt und die n Nullen durch Einsen (100b - 1 = 011b). Womit wir die gewünschte Netzmaske hätten.
Klappt auch wunderbar. Mit einer Ausnahme: (1 << 32) - 1 = 0.
Bedeutet in der Praxis: Aus jedem /32 Netz - einer einzelnen IP - wird ein /0 Netz - also der gesamte Adressraum des Internets. Solch eine Situatuation ist fatal. Man stelle sich vor, dass ein Datenbankserver von 127.0.0.1/32 Administrationszugriff ohne Passwort erlaubt. Durch diesen Bug hätte jeder Besucher Vollzugriff...
Das Mysteriöse an diesem Bug war, dass er sich nicht reproduzieren ließ, wenn man direkt 32 einsetzt. (1 << 32) - 1 = 0xFFFFFFFF, aber (1 << getThirtyTwo()) - 1 = 0. Das stinkt nach einem Compilerbug.
Nachdem weder ein Umstellen des Codes, ein isoliertes Testen oder Deaktivierung der Codeoptimierung Erfolg brachte habe ich mir angeschaut, welcher Assembler-Code generiert wird:

Der Code ist korrekt. Was zum Teufel...?!
Auf die Lösung gebracht hat mich dann eine kurze Google-Suche:
The 8086 does not mask the shift count. However, all other IA-32 processors (starting with the Intel 286 processor) do mask the shift count to 5 bits, resulting in a maximum count of 31. This masking is done in all operating modes (including the virtual-8086 mode) to reduce the maximum execution time of the instructions.
http://siyobik.info/index.php?module=x86&id=285
Im Klartext: Die CPU schmeißt meinen Optimierungsversuch dezent weg. Bitter. Ein Fix ist hier die Fallunterscheidung, ob man 32 Bits shiften will. Da (1 << 32) statisch berechenbar ist, greift hier der Optimizer des Microsoft Compilers. Aber interessanterweise verhält sich dieser halt anders als die CPU. Mal sehen was Microsoft dazu sagt - werde das die Tage mal melden...
Lektion des Tages: Nichts ist so trivial, als dass man es nicht doch lieber testen sollte...
compilerbug? ist die rückgabe von getThirtyTwo ein unsigned int?
Jop, ist unsigned int. Sollte in dem Kontext aber auch egal sein.
Compilerbug auf jeden Fall, die statische Optimierung verfälscht das Ergebnis.
Hab von MS noch keine Rückmeldung, sie werdens denk ich aber auch nicht fixen. Wer weiß wie viel Software sich darauf verlässt…
Eine Alternative zur Fallunterscheidung wäre auch die Unterteilung des Shiftings in zwei Teile. Soll heißen, statt direkt um 32 Bits nach links zu schieben, schiebst bspw. zuerst nur 30 und dann die restlichen 2.
E.g.:
unsigned int x = getThirtyTwo();
unsigned int y = (1 << (x – ( x % 10)) << (x % 10)) – 1;
Wie sich das von der Performance her im Vergleich zur Fallunterscheidung verhält sei mal dahingestellt.
Ist sicherlich auch eine Maßnahme, aber rein von der Lesbarkeit des Codes her würde ich dann doch die Fallunterscheidung bevorzugen.
EY DIGGA MACH NEUEN POST, der scheiß steht jetz schon seit nem Jahr auf Seite.!!!!
Ist in Planung