expertenaustausch > comp.lang.* > comp.lang.c

Оlе Ѕtrеісhеr (16.05.2017, 06:03)
Hallo,

ich habe hier (30 Jahre alten) Code, der folgendes macht [1]:

#define ADDR_TO_LOC(addr) ((int)((short *)(addr)))>>(sizeof(short)-1)

Es wird also praktisch die Adresse um ein bit nach rechts verschoben und
dann nach "int" konvertiert. Im Weiteren wird angenommen, dass das
Ergebnis positiv ist.

Gibt es irgendeine Aussage darüber, ob ei Pointer signed oder unsigned
ist und ob das Vorzeichen ins MSB kopiert wird?

Schöne Grüße

Ole

[1]
Peter J. Holzer (16.05.2017, 09:42)
On 2017-05-16 04:03, ?l? ?tr???h?r <ole-usenet-spam> wrote:
> ich habe hier (30 Jahre alten) Code, der folgendes macht [1]:
> #define ADDR_TO_LOC(addr) ((int)((short *)(addr)))>>(sizeof(short)-1)
> Es wird also praktisch die Adresse um ein bit nach rechts verschoben und
> dann nach "int" konvertiert.


Nein, es wird zuerst nach int konvertiert und dann verschoben. Der Cast
bindet stärker als der Shift-Operator. (Außerdem ist >> nur auf
Integer-Typen definiert, der Versuch, einen Pointer zu shiften wäre also
eine Constraint-Violation).

> Im Weiteren wird angenommen, dass das
> Ergebnis positiv ist.
> Gibt es irgendeine Aussage darüber, ob ei Pointer signed oder unsigned
> ist und ob das Vorzeichen ins MSB kopiert wird?


Pointer sind weder signed noch unsigned. Die Konversion in Integer-Typen
ist implementation-defined. Alles was wir wissen, ist dass es mindestens
einen Integer-Typ gibt, für den die Konversion (void *) -> T -> (void *)
verlustfrei ist.

In der Praxis ist bei 32-Bit-Architekturen meistens die Konversion von
einem beliebigen Pointer nach int und zurück verlustfrei möglich.
Außerdem ist der Adressraum linear und die "Konversion" ist einfach nur
eine Kopie. In diesem Fall funktioniert dieser Code, wenn jeder Pointer
in die ersten 2 GB des virtuellen Adressraums zeigt. Das war
traditionell bei vielen Unixen der Fall, aber nicht bei allen: In
Linux/i386 stehen dem Prozess üblicherweise 3 GB virtueller Adressraum
zur Verfügung. Dort würde der Code also auf die Nase fallen, weil das
Ergebnis des Casts negativ sein kann und das negative Vorzeichen beim
Shift erhalten bleibt (bei den üblichen C-Compilern).

Auf 16-Bit-Architekturen dürfte das vor 30 Jahren schon nicht
funktioniert haben.

> [1]


"Machine dependent definitions for the 4.1BSD UNIX IRAF Kernel."

Auf BSD 4.1 dürften die getroffenen Annahmen (32-Bit-Architektur, <= 2
GB linearer Adressraum, ...) für alle Systeme zugetroffen haben.

hp
Helmut Schellong (16.05.2017, 11:58)
On 05/16/2017 06:03, ?l? ?tr???h?r wrote:
> Hallo,
> ich habe hier (30 Jahre alten) Code, der folgendes macht [1]:
> #define ADDR_TO_LOC(addr) ((int)((short *)(addr)))>>(sizeof(short)-1)
> Es wird also praktisch die Adresse um ein bit nach rechts verschoben und
> dann nach "int" konvertiert. Im Weiteren wird angenommen, dass das
> Ergebnis positiv ist.
> Gibt es irgendeine Aussage darüber, ob ei Pointer signed oder unsigned
> ist und ob das Vorzeichen ins MSB kopiert wird?


Ich finde den Code zu spezifisch, nicht generisch genug.

((int)...) >> 1

Daß da erst geschoben wird und danach nach 'int' gecastet wird, kann
nicht sein, schon allein wegen der ().

Ich würde grundsätzlich den Typ 'uintptr_t' verwenden (hier statt 'int').

Vorzeichenbehaftete Adressen hatte es wohl mal gegeben oder gibt es
immer noch (intptr_t) - aber diese wären auf jeden Fall exotisch.
Claus Reibenstein (16.05.2017, 22:35)
Peter J. Holzer schrieb am 16.05.2017 um 09:42:

> On 2017-05-16 04:03, ?l? ?tr???h?r <ole-usenet-spam> wrote:
>> #define ADDR_TO_LOC(addr) ((int)((short *)(addr)))>>(sizeof(short)-1)
>> Es wird also praktisch die Adresse um ein bit nach rechts verschoben und
>> dann nach "int" konvertiert.

> Nein, es wird zuerst nach int konvertiert und dann verschoben. Der Cast
> bindet stärker als der Shift-Operator.


Die Bindung wird durch die Klammern bestimmt, nicht durch die
Prioritäten der beteiligten Operatoren.

Gruß
Claus
Peter J. Holzer (18.05.2017, 18:27)
On 2017-05-16 20:35, Claus Reibenstein <4spamersonly> wrote:
> Peter J. Holzer schrieb am 16.05.2017 um 09:42:
> Die Bindung wird durch die Klammern bestimmt, nicht durch die
> Prioritäten der beteiligten Operatoren.


Stimmt. Da habe ich offensichtlich gesehen, was sinnvoll gewesen wäre,
nicht was da stand.

Ich hoffe, es hat nie jemand dieses Makro in einem Ausdruck verwendet.
x = ADDR_TO_LOC(p) + 2
tut nämlich nicht, was man erwarten würde.

hp
Оlе Ѕtrеісhеr (21.05.2017, 22:03)
"Peter J. Holzer" <hjp-usenet3> writes:
>>> On 2017-05-16 04:03, ?l? ?tr???h?r <ole-usenet-spam> wrote:
>>>> #define ADDR_TO_LOC(addr) ((int)((short *)(addr)))>>(sizeof(short)-1)


> Ich hoffe, es hat nie jemand dieses Makro in einem Ausdruck verwendet.
> x = ADDR_TO_LOC(p) + 2
> tut nämlich nicht, was man erwarten würde.


Zum Glück nicht. Die einzige Anwending ist

*buf = ADDR_TO_LOC(bufptr);

Ich will aber nicht sagen, dass ich besonders glücklich über diesen
Codehaufen bin :-( Aber was macht man mit >>30 Jahre altem Code, der
fachlich heute noch benötigt wird?

Es gibt da noch mehr schöne "Überraschungen", z.B. eine
Arraydeklaration als

struct {
/* das ist eigentlich ein mem[60000], das weiß man hier nur nicht genau */
int mem[1];
} memptr;

Aus der Deklaration folgern moderne Compiler offenbar, dass es sich nur
um eine einzige Zahl handeln kann, und optimieren bei
aufeinanderfolgenden Zuweisungen schon mal die erste weg:

....
memptr.mem[i1] = a; // das verschwindet bei Optimierung
memptr.mem[i2] = b;
....

Gruselcode das alles... sowas findet leider auch kein Codechecker
(oder?)

Danke jedenfalls an alle für die Antworten.

Schöne Grüße

Ole
Оlе Ѕtrеісhеr (21.05.2017, 22:23)
ole-usenet-spam (?l? ?tr???h?r) writes:
>>>> On 2017-05-16 04:03, ?l? ?tr???h?r <ole-usenet-spam> wrote:
>>>>> #define ADDR_TO_LOC(addr) ((int)((short *)(addr)))>>(sizeof(short)-1)

> Danke jedenfalls an alle für die Antworten.


Als Zusatzfrage hätte ich aber noch: wie kann man denn portabel ein
rechtsshifting (mit beliebiger bitzahl) garantiert eine Null
nachgeschoben wird?

Schöne Grüße

Ole
Rainer Weikusat (21.05.2017, 23:56)
ole-usenet-spam (?l? ?tr???h?r) writes:
> ole-usenet-spam (?l? ?tr???h?r) writes:
> Als Zusatzfrage hätte ich aber noch: wie kann man denn portabel ein
> rechtsshifting (mit beliebiger bitzahl) garantiert eine Null
> nachgeschoben wird?


Vorzeichenlose Typen benutzen.
Rainer Weikusat (21.05.2017, 23:59)
ole-usenet-spam (?l? ?tr???h?r) writes:

[...]

> struct {
> /* das ist eigentlich ein mem[60000], das weiß man hier nur nicht genau */
> int mem[1];
> } memptr;
> Aus der Deklaration folgern moderne Compiler offenbar, dass es sich nur
> um eine einzige Zahl handeln kann, und optimieren bei
> aufeinanderfolgenden Zuweisungen schon mal die erste weg:
> ...
> memptr.mem[i1] = a; // das verschwindet bei Optimierung
> memptr.mem[i2] = b;


Hmm ... das bezweifle ich jetzt mal: Traditionall konnte man in C keine
Felder mit Groesse 0 deklarieren, vgl 'info gcc' 5.14. Insofern war/ ist
das ein ueblicher Workaround fuer 'Feld variabler Laenge'.
Оlе Ѕtrеісhеr (22.05.2017, 08:17)
Rainer Weikusat <rweikusat> writes:
> ole-usenet-spam (?l? ?tr???h?r) writes:
> Vorzeichenlose Typen benutzen.


Soweit ich weiß, ist es auch bei vorzeichenlosen Typen
implementationsabhängig. Der gcc macht das so, clang offenbar auch. Aber
wie wäre eine Lösung in ISO-C?

Ole
Оlе Ѕtrеісhеr (22.05.2017, 08:20)
Rainer Weikusat <rweikusat> writes:
> ole-usenet-spam (?l? ?tr???h?r) writes:
> [...]
> Hmm ... das bezweifle ich jetzt mal: Traditionall konnte man in C keine
> Felder mit Groesse 0 deklarieren, vgl 'info gcc' 5.14. Insofern war/ ist
> das ein ueblicher Workaround fuer 'Feld variabler Laenge'.


Einer, der nicht standardkonform ist und "neuerdings" (so seit gcc-4.8)
auch nicht mehr funktioniert.

Das Beispiel zeigt sehr gut, wie ein "passt schon irgendwie, habe es
kompiliert und es ging alles" einem irgendwann auf die Füße fällt und
sehr schwer zu findende Fehler verursacht. Die übliche Reaktion ist
dann, laut "Compilerbug!!!" zu rufen, dabei ist man selber schuld.

Ole
Claus Reibenstein (22.05.2017, 08:47)
?l? ?tr???h?r schrieb am 22.05.2017 um 08:17:

> Rainer Weikusat <rweikusat> writes:
> Soweit ich weiß, ist es auch bei vorzeichenlosen Typen
> implementationsabhängig.


Implementationsabhängig ist es nur bei vorzeichenbehafteten Typen, und
dort auch nur dann, wenn der aktuelle Wert negativ ist. Das war schon in
C99 so:

| The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1
| has an unsigned type or if E1 has a signed type and a nonnegative
| value, the value of the result is the integral part of the quotient
| of E1 / 2E2. If E1 has a signed type and a negative value, the
| resulting value is implementation-defined.

(Quelle: ISO/IEC 9899/1999, §6.5.8, Punkt 5)

> Der gcc macht das so, clang offenbar auch. Aber wie wäre eine Lösung
> in ISO-C?


Das _ist_ die Lösung in ISO-C.

Gruß
Claus
Оlе Ѕtrеісhеr (22.05.2017, 09:14)
Claus Reibenstein <4spamersonly> writes:
[..]
> | resulting value is implementation-defined.
> (Quelle: ISO/IEC 9899/1999, §6.5.8, Punkt 5)
> Das _ist_ die Lösung in ISO-C.


OK, danke. Ich hatte das anders in Erinnerung.

Schöne Grüße

Ole
Оlе Ѕtrеісhеr (22.05.2017, 12:24)
Rainer Weikusat <rweikusat> writes:
> ole-usenet-spam (?l? ?tr???h?r) writes:
>> memptr.mem[i1] = a; // das verschwindet bei Optimierung
>> memptr.mem[i2] = b;

> Hmm ... das bezweifle ich jetzt mal: Traditionall konnte man in C keine
> Felder mit Groesse 0 deklarieren, vgl 'info gcc' 5.14. Insofern war/ ist
> das ein ueblicher Workaround fuer 'Feld variabler Laenge'.


Ich habe mal das in konkreten selbständigen Code gegossen: 2
Quelldateien;

1. mem.c
-------------------->8------- mem.c --------------------------
#include <stdio.h>

struct {
int m[2000];
} mem;

void printmem(int i) {
printf("%i %i\n", i, mem.m[i]);
}
-------------------->8------- mem.c --------------------------

2. main.c
-------------------->8------- main.c -------------------------
#include <stdlib.h>
void printmem(int i);

extern struct {
int m[1];
} mem;

int main(int argc, const char ** argv) {
int i, a1, a2;
a1 = atoi(argv[1]);
a2 = atoi(argv[2]);
mem.m[a1] = a2;
mem.m[a2] = a1;

printmem(a1);
printmem(a2);
return 0;
}
-------------------->8------- main.c -------------------------

Beide mit Optimierung kompilieren, zusammenlinken und dann starten:

$ gcc -O2 -o main mem.c main.c
$ ./main 12 34
12 0
34 12

Ohne Optimierung, oder mit der korrekten Deklaration in main.c ist alles
OK.

Schöne Grüße

Ole
Bastian Blank (22.05.2017, 13:44)
?l? ?tr???h?r wrote:
> Rainer Weikusat <rweikusat> writes:
> Ich habe mal das in konkreten selbständigen Code gegossen: 2
> Quelldateien;


Bitte lies noch mal was Rainer geschrieben hat.

> extern struct {
> int m[1];
> } mem;
> int main(int argc, const char ** argv) {
> int i, a1, a2;
> a1 = atoi(argv[1]);
> a2 = atoi(argv[2]);
> mem.m[a1] = a2;
> mem.m[a2] = a1;


mem.m is genau ein Element lang. Du kannst also nur "mem.m[0]"
beschreiben, alles andere ist UB.

> Beide mit Optimierung kompilieren, zusammenlinken und dann starten:
> $ gcc -O2 -o main mem.c main.c
> $ ./main 12 34
> 12 0
> 34 12


Vollkommen korrektes Verhalten.

Bastian

Ähnliche Themen