Hallo,
sind folgende 2 Ausdrücke die gleichen?
// ptr ist ein Pointer auf ein konstantes void
const void * ptr
void const * ptr
Das untere ist etwas schöner zu lesen, denke ich.
Gruß!
Nein,
Das eine ist ein konstanter Pointer auf sich Void Daten, die sich ändern
können
Das andere ist ein änderbarer Pointer, der auf konstante Void Daten
zeigt.
Timm schrieb:
Hier hilft http://cdecl.org/> const void * p
declare p as pointer to const void
> void const * p
syntax error
> void * const p
declare p as const pointer to void
Mark Brandis schrieb:>> void const * p>> syntax error
das kann ich nicht bestätigen. Mit meinem Compiler ist diese Reihenfolge
egal.
Wichtig ist nur was vor und was nach dem Stern steht.
Es empfiehlt sich auch die zweite Schreibweise. Warum? Beispiel dazu:
typedef char* CHARS;
1> typedef CHARS const CPTR;
im Gegensatz zu
2> typedef const CHARS CPTR;
Wenn man CHARS ersetzt, sieht man den Unterschied:
1> typedef char* const CPTR;
2> typedef const char* CPTR;
Thomas K. schrieb:> Nein, ein konstanter pointer auf void Daten wäre>> void * const ptr
Wenn er Recht hat, hat er Recht.
Sorry, sollte wohl genauer hinschauen.
Mark Brandis schrieb:>> void const * p>> syntax error
Ist aber korrektes C. Die Reihenfolge der declaration-specifier ist
nicht vorgeschrieben. So ist statt
extern const int var;
ebenso zulässig:
int const extern var;
Thomas K. schrieb:> Es empfiehlt sich auch die zweite Schreibweise.
Das ist ein etwas an den Haaren herbeigezogenes Argument
denn in deinem Beispiel ist es auf jeden Fall immer der Pointer der
konstant ist.
Man kann nämlich auch als Fautregel angeben:
Grundsätzlich empfiehlt es sich, einen Pointer Datentyp nicht in einem
typedef zu verstecken (und auf keinen Fall in einem Makro!). Das gibt
auf lange Sicht gesehen nämlich meistens Ärger und Verwirrung.
Karl heinz Buchegger schrieb:> Grundsätzlich empfiehlt es sich, einen Pointer Datentyp nicht in einem> typedef zu verstecken
Warum? Das sehe ich durchaus anders. Es wäre aber schon praktisch, wenn
man dem Namen ansieht, dass es sich um einen Pointer handelt.
> (und auf keinen Fall in einem Makro!)
D'accord.
A. K. schrieb:> Thomas K. schrieb:>>> Wenn man CHARS ersetzt, sieht man den Unterschied:>> typedef ist keine Textersetzung.
Habe ich nie behauptet. Die Aussage war im Sinne von "wenn man das ganze
ohne den typedef direkt mit den Typen schreibt"
Karl heinz Buchegger schrieb:> Das ist ein etwas an den Haaren herbeigezogenes Argument> denn in deinem Beispiel ist es auf jeden Fall immer der Pointer der> konstant ist.
falsch, denn 1> ist ein konstanter pointer to char und 2> ist ein
pointer zu einem konstanten char. Somit ist bei 2> nicht der pointer
konstant.
Das Beispiel soll nur das Problem verdeutlichen. Bei dem hier genannten
Beispiel ist dies irrelevant, aber typedefs werden u.a. bei Templates
immer oft und gerne genutzt und dort kann das von mir genannte deutliche
Unterschiede machen.
A. K. schrieb im Beitrag #1791567:
> Thomas K. schrieb:>>> Habe ich nie behauptet. Die Aussage war im Sinne von "wenn man das ganze>> ohne den typedef direkt mit den Typen schreibt">> Stand so nicht da. Das war dort stand war schlicht falsch.
Es steht unverändert oben und wenn man sich nur den Teilsatz anstatt dem
ganzen Satz ansieht, kann man sich das natürlich sonstwie hin
interpretieren. "Wenn man CHARS ersetzt" sagt mit keinem Wort aus, dass
das typedef eine Textersetzung wäre. Aber man kann natürlich alles immer
so lesen und verstehen, dass man was zum meckern hat...
Thomas K. schrieb:> A. K. schrieb:>> Thomas K. schrieb:>> Das ist ein etwas an den Haaren herbeigezogenes Argument>> denn in deinem Beispiel ist es auf jeden Fall immer der Pointer der>> konstant ist.>> falsch, denn 1> ist ein konstanter pointer to char und 2> ist ein> pointer zu einem konstanten char. Somit ist bei 2> nicht der pointer> konstant.
Hmm
Der Compiler sieht die Sache aber anders
1
#include<stdio.h>
2
#include<stdlib.h>
3
4
5
typedefchar*CHARS;
6
typedefconstCHARScptr_1;
7
typedefCHARSconstcptr_2;
8
9
intmain()
10
{
11
cptr_1ptr1="Hallo";
12
cptr_2ptr2="World";
13
14
ptr1="Juhu";// <----
15
ptr2="Kukuck";// <----
16
17
*ptr1='A';
18
*ptr2='B';
19
}
Beide Zuweisungen an die Pointer werden mit
"error C2166: l-value specifies const object"
quittiert.
Die Character darf ich allerdings in beiden Fällen ändern :-)
Fazit: In beiden Schreibweisen ist es der Pointer der konstant ist.
Siehe es so - der typedef legt um den Datentyp so etwas ähnliches wie
eine gedankliche Klammer. CHARS ist in erster Linie ein
Pointer-Datentyp. jegliche Modifier wirken daher auf den Pointer.
Wie weiter oben schon gesagt wurde: typedef macht keine Textersetzungen!
Sobald du daher versuchst das Verhalten mit Textersetzung zu erklären,
kannst du Schiffbruch erleiden.
zb sind in
1
typedefchar*CHARS;
2
CHARSi,j,k,l;
alle Variablen i, j, k und l vom Datentyp Pointer to char.
während in
1
#define CHARS char *
2
CHARSi,j,k,l;
genauso wie in
1
char*i,j,k,l;
nur i ein Pointer ist und j, k und l normale char sind.
A.K. siehst du jetzt, warum ein typedef für einen Pointer oft keine gute
Idee ist. Wie ich schon sagte: führt oft auf lange Sicht zu Ärger und
Verwirrung.
Karl heinz Buchegger schrieb:> A.K. siehst du jetzt, warum ein typedef für einen Pointer oft keine gute> Idee ist.
Für mich entstand hier kein bischen Verwirrung, aber ich erkenne, dass
andere dadurch offensichtlich verwirrt werden.
Übrigens ist die Schreibweise mit "const" hinter dem Typ in C++ üblich
denn man findet sie regelmässig bei Referenzen, z.B. "int const& x".
> Wie ich schon sagte: führt oft auf lange Sicht zu Ärger und> Verwirrung.
Was dagegen mich früher verwirrte, war die Schreibweise
int far *p; //1
statt
int *far p; //2
denn aus syntaktischer Sicht ist (1) blühender Unsinn, gehört das "far"
doch eigentlich logisch zum Pointer. Es kann in dieser Form aber
aufgrund der syntaktischen Struktur unmöglich dem "*" sondern nur dem
"int" zugeordnet werden. Da hatte jemand wohl krampfig versucht, es für
Leuten mit weniger Einblick in die syntaktische Struktur lesbarer zu
machen.
A. K. schrieb:> Für mich entstand hier kein bischen Verwirrung, aber ich erkenne, dass> andere dadurch offensichtlich verwirrt werden.
Yep.
Und ja: Ich hatte auch eine Phase, in der ein
1
typedefstruct
2
{
3
intirgendwas;
4
charwas_anderes;
5
}
6
my_struct;
7
typedefmy_struct*my_struct_ptr;
ganz normal war, wie wahrscheinlich bei vielen anderen auch. Habs aber
dann wieder aufgegeben. Es stand einfach nicht dafür. Der * in der
Deklaration leuchtet aus irgendeinem Grund besser aus dem Monitor
heraus, als es jegliche _ptr oder _Ptr oder wie auch immer Anhängsel je
könnten. Ich gesteh aber gerne zu, dass das hauptsächlich Gewohnheit
ist.
> Was dagegen mich früher verwirrte, war die Schreibweise> int far *p;> denn aus syntaktischer Sicht ist das blühender Unsinn, gehört das "far"> doch eigentlich zum Pointer. Es kann in dieser Forme aber aufgrund der> syntaktischen Struktur nicht dem "*" sondern nur dem "int" zugeordnet> werden.
Yep.
Der Kelch ist Gottseidank an mir vorbei gegangen.
Wir sind gerade von MS-C auf Watcom-C gewechselt, als die far Pointer
bei MS essentiell wurden. Und in Watcom-C war das kein Thema.
Grundsätzlich kann man sagen:
Ein const bezieht sich immer auf den Typ, der unmittelbar links davon
steht. Steht da nichts, dann bezieht es sich auf das, was unmittelbar
rechts davon steht.
Gruss
Simon
Dadurch dass in der Deklaration die Qualifier, Type und Storage Class
keine bestimmte Reihenfolge einhalten müssen, ist übrigens auch sowas
erlaubt:
1
inttypedefconstfoo;
"typedef" zählt syntaktisch in C als storage-class-specifier, auch wenn
es semantisch nichts mit der Storage Class zu tun hat.
Allerdings gehört das wohl auch zu den Dingen, die man sich im Sinne der
Lesbarkeit des Codes eher verkneifen sollte.
Andreas
Karl heinz Buchegger schrieb:> genauso wie in>> char * i, j, k, l;>> nur i ein Pointer ist und j, k und l normale char sind.
Noch etwas weitergehend ist auch in
1
char*consti,j,k,l;
nur i konstant, während j, k und l veränderbar sind.
Andreas
Klaus Wachtler schrieb:> ebenso wie bei> int const * i = 0, j = 0;> ist nur i const, j nicht.
Nein, in letzterem Fall ist j const, i nicht (aber *i).
Andreas
Klaus Wachtler schrieb:> sorry, stimmt natürlich.> Aber trotzdem nicht schön gelöst in C.
Seh' ich auch so. Bezüglich const macht die C-Syntax ein bisschen ein
Bordell. Wenn man const grundsätzlich dem, was "verkonstet" werden soll,
nachstellt, wird's einigermassen einheitlich:
Aber
int const i=0
sieht halt bescheiden aus (wenn's auch korrekt ist).
Aber grundsätzlich ist die oben genannte Regel sehr gut geeignet, das
Bordell mit den const zu durchschauen:
Klaus Wachtler schrieb:> Was ich skuril finde:>> Bei>
1
>intconsti=0,j=0;
2
>
> sind sowohl i als auch j const.>
links von const ist ein int. also werden zwei konstante ints deklariert.
> Dagegen bei>
1
>int*consti=0,j=0;
2
>
links von const ist ein int*. Also werden zwei konstante Pointer auf
(variable) ints deklariert.
> ebenso wie bei>
1
>intconst*i=0,j=0;
2
>
> ist nur i const, j nicht.
hier ist eben links von const wieder nur ein int. Also werden zwei
konstante ints deklariert. Einer, der 'nen Pointer namens i drauf hat,
und einen namens j.
1
intconst*consti
deklariert jetzt dementsprechend einen konstanten Pointer auf eine
Konstante
Doof ist jetzt einfach, dass die Regel etwas lasch ist, und dass const
eben auch links stehen kann statt rechts (wobei es aber immer erst nach
links "schaut"). Und dummerweise wird gerade im allerallerersten Fall,
der in den Lehrbüchern steht, von dieser Ausnahmeregelung gebrauch
gemacht.
Simon Huwyler schrieb:>> int * const i = 0, j = 0;>>> links von const ist ein int*. Also werden zwei konstante Pointer auf> (variable) ints deklariert.
Und prompt in die Falle getappt ;-) j ist hier ein einfacher int, es ist
weder ein Pointer noch const.
Simon Huwyler schrieb:> Seh' ich auch so. Bezüglich const macht die C-Syntax ein bisschen ein> Bordell. Wenn man const grundsätzlich dem, was "verkonstet" werden soll,> nachstellt, wird's einigermassen einheitlich:
Eigentlich muss man sich nur eins klar machen: alles ab einschliesslich
dem "*" gehört nicht mehr zu den "declaration-specifiers", sondern ist
Teil des "declarator". Deshalb wird das dann auch nicht in einer
Deklaration von mehreren Variablen an die anderen in der Liste
"weitergereicht". Damit bezieht sich ein const vor dem ersten "*" auf
alle Variablen in der Liste, ein const zwischen "*" und Identifier
dagegen nur auf die einzelne.
Der Knackpunkt an der Sache, der diese Syntax-"Verrenkungen" nötig
macht, ist der, dass bei einer Pointer-Deklaration wenn man so will
zwei Objekte auf einmal deklariert werden, erstens der Pointer selbst
und zweitens das Ziel dieses Pointers. Die "Trennstelle" zwischen den
Teilen ist im Programmcode das "*"-Zeichen.
Exakt genauso verhält es sich auch bei Array- oder
Funktionsdeklarationen, allerdings ist das identische Verhalten da ein
wenig intuitiver:
1
constinti[10],j(int),k;
Hier ist i ein Array (von konstanten ints), j eine Funktion mit einem
int-Parameter und einem const int als Rückgabewert, und k ein einzelner
konstanter int.
Zur Erhöhung der Lesbarkeit ist es aber meist empfehlenswert,
Deklarationen nur dann zusammenzuziehen, wenn diese auch wirklich im
Ergebnis gleich sind:
1
constinti1,i2;
2
constint*p1,*p2;
und /nicht/:
1
constinti1,i2,*p1,*p2;
Ein "Trost" ist vielleicht auch noch, dass konstante Pointer tendenziell
eher selten anzutreffen sind (wenn dann meist nur als
Funktionsparameter), konstante Pointer- Ziele sind wesentlich
häufiger.
Andreas
Andreas Ferber schrieb:> Simon Huwyler schrieb:>>> int * const i = 0, j = 0;>>>>> links von const ist ein int*. Also werden zwei konstante Pointer auf>> (variable) ints deklariert.>> Und prompt in die Falle getappt ;-) j ist hier ein einfacher int, es ist> weder ein Pointer noch const.
grrrrrrrrrrrrrrrrrrrrrr.... verdammmt! Hast natürlich recht!
Die Deklarationssyntax von C war von Anfang an eine Fehlkonstruktion.
Die ursprüngliche Idee dahinter, die Deklarationssyntax wie die
Verwendung aussehen zu lassen, hat zu einer katastrophalen Syntax
geführt, die in diesem Leben wohl leider nicht zu retten ist.
Eigentlich sollte es die Sache vereinfachen. Praktisch führt es dazu,
dass Mensch ähnlich wie ein maschineller Parser entlang der formalen
Sprachstrukturen unter Berücksichtigung von der operator precedences das
Statement analysieren muss. Mag also sein, dass jemand, der sich bereits
auf dieser Ebene näher mit C befasst hat, es beim Verständnis davon
etwas leichter hat.
Wenn man das mit der blitzsauberen Methodik der Wirth'schen
Sprachfamilie vergleicht, in denen in schlichter linearer Weise von
links nach rechts hingeschrieben wird, was man meint...
A. K. schrieb:> Die Deklarationssyntax von C war von Anfang an eine Fehlkonstruktion.> Die ursprüngliche Idee dahinter, die Deklarationssyntax wie die> Verwendung aussehen zu lassen, hat zu einer katastrophalen Syntax> geführt, die in diesem Leben wohl leider nicht zu retten ist.
Und ich dachte schon, ich bin der Einzige der für derart ketzerische und
unkeusche Gedanken auf den Scheiterhaufen kommen wird.
> Ich bin halt feige und nehme im Zweifel ein paar mehr Deklarationen
Mach ich auch so.
Karl heinz Buchegger schrieb:> Und ich dachte schon, ich bin der Einzige der für derart ketzerische und> unkeusche Gedanken auf den Scheiterhaufen kommen wird.
Ich hatte das Vergnügen, einen K&R-Parser in yacc die ANSI-Syntax
beizubringen. Da bringt einem solche Spässe manchmal näher als einem
lieb ist. Und es schärft den Sinn für syntaktische Aspekte. So hatte
Stroustrup mit C++ leider die K&R-Tradition der syntaktischen
Stolpersteine nahtlos fortgesetzt. Ich erinnere mich noch gut dran, wie
mir bei der Lektüre seines Buches die Haare zu Berge standen.
Jörg Wunsch schrieb:> Fällt wohl in die gleiche Kategorie wie sowas:> int foo[20];>> int> getfoo(int i)> {> return i[foo];> }
Und dabei meckert der Compiler nicht, wenn man einen [] auf einen
nicht-Array und nicht-Pointer-Typ loslässt? =-O
Klaus schrieb:> Und dabei meckert der Compiler nicht, wenn man einen [] auf einen> nicht-Array und nicht-Pointer-Typ loslässt? =-O
Nö.
a[i] <==> *( a + i ) <==> *( i + a ) <==> i[a]
So wie Arrayindizierung in C definiert ist, ist das eine logische
Konsequenz. Der Compiler muss a[i] zuallererst in *( a + i )
umformen. Alles andere folgt dann.
Klaus schrieb:> Und dabei meckert der Compiler nicht, wenn man einen [] auf einen> nicht-Array und nicht-Pointer-Typ loslässt? =-O
Schau dir die Definition des Operators im Standard an. Das hängt mit der
Definition von Arrayzugriffen auf dem Weg über Pointerarithmetik
zusammen.
Das Subskripting in dem Beispiel ist per Definition im Standard
identisch zu:
1
return*(i+foo);
Aufgrund der Kommutativität des +-Operators ist das wiederum identisch
zu
1
return*(foo+i);
und das wiederum zu
1
returnfoo[i];
Das einzig relevante ist, dass bei der Addition irgendetwas rauskommt,
was mit dem *-Operator dereferenziert werden kann.
Andreas
Andreas Ferber schrieb:> return *(i+foo);>> Aufgrund der Kommutativität des +-Operators ist das wiederum identisch> zu> return *(foo+i);
Hier liegt IMHO der Hund für die Verwirrung bei der Arrayindizierung
begraben. Da bei der Pointeraddition zwei völlig unterschiedliche
Datentypen miteinander verknüpft werden, erwartet man hier nicht
unbedingt Kommutativität. Ich würde nicht einmal erwarten, dass die
Operation Integer+Pointer überhaupt definiert ist. Wäre sie es nicht,
wäre es Integer[Pointer] ebenfalls nicht, und die Verwirrung würde gar
nicht erst entstehen.
Man hätte aber auch ganz einfach fordern können, dass bei
a[i]
das a entweder ein Array oder ein Pointer Typ sein muss. Und zwar noch
ehe man die Konversion nach *(a+i) macht.
Aber dann wären wieder 0.0005% aller C-Programme (OK, im OCCC wären es
mehr gewesen) nicht mehr kompatibel gewesen und 2 von 5 Millionen
C-Programmierer hätten aufgeheult, weil sie ihr geliebtes
digit = i["0123456789"];
nicht mehr benutzen können.
Das K&R diese Forderung nicht erhoben haben, kann ich noch verstehen.
Aber das das C-Kommitee da nicht irgendwann einmal selbst über den
eigenen Schatten springt und die vielbeschworene "Abwärtskompatibilität,
koste es was es wolle und egal wie unsinnig es ist" um jeden Preis
aufrecht erhält, das verstehe ich nicht.
Und bei C++ ist es auch nicht anders :-)
Karl heinz Buchegger schrieb:> Und bei C++ ist es auch nicht anders :-)
Gut, die bisher vorgebrachten Beispiele sind mehr akademischer Natur,
gebe ich zu.
Daher C++
1
classParamaterSet
2
{
3
public:
4
5
voidSet(constchar*name,intvalue);
6
voidSet(constchar*name,doublevalue);
7
voidSet(constchar*name,constchar*value);
8
};
9
10
intmain()
11
{
12
ParameterSetparams;
13
...
14
15
params.Set("Anzahl",5);
16
params.Set("Name","Hugo");
17
params.Set("Name",NULL);
18
}
schnell, ohne lang zu analysieren, beantworte man die Frage: welche
Funktion wird beim letzten Set Aufruf aufgerufen.
Wer
void ParamaterSet::Set( const char * name, const char * value );
sagt, liegt leider falsch.
NULL hat in C++, obwohl explizit für Pointerverwendung vorgesehen, den
Datentyp int. Das man nicht straffrei einen void* an jeden anderen
beliebigen Datenpointer zuweisen darf, ist IMHO ein Fortschritt,
trotzdem hätte man für NULL (der explizit als void* hätte definiert
werden können) die Erlaubnis geben können. Es wäre zwar eine
Ausnahmeregelung gewesen, aber eine IMHO sinnvolle.
König Lookup muss man kennen
1
classA
2
{
3
public:
4
voidfoo(intvalue);
5
};
6
7
classB:publicA
8
{
9
public:
10
voidfoo(doublevalue);
11
};
12
13
intmain()
14
{
15
Bobj;
16
17
obj.foo(5);
18
}
welches foo wird benutzt?
Es ist B::foo( double )
Überraschend? Für Neulinge, die sich nie damit beschäftigt haben schon,
ist doch offenbar A::foo( int ) ein viel besserer Match für eine beim
Aufruf angegebene 5
"Wenn etwas eine Deklaration sein kann, dann ist sie das auch"
Von der eigenartigen Ausnahme, dass main(), obwohl der Returntyp int
sein muss, kein explizites return braucht, red ich schon gar nicht mehr.
Und so gibt es noch viele andere Dinge.
Wie weiter oben schon vorgebracht wurde: Bei N.Wirth und seinen Sprachen
gibt es solche auf den ersten Blick seltsamen Sachen nicht.
Karl heinz Buchegger schrieb:> werden können) die Erlaubnis geben können. Es wäre zwar eine> Ausnahmeregelung gewesen, aber eine IMHO sinnvolle.
Wäre auch ohne Ausnahmeregelung gegangen. "null" (oder "nil") als
Keyword, mit implizitem eigenem zu allen Pointern zuweisungskompatiblem
Datentyp. Und schon hätte Stroustrup ein verhasstes #define weniger
gehabt.
A. K. schrieb:> Wäre auch ohne Ausnahmeregelung gegangen. "null" (oder "nil") als> Keyword, mit implizitem eigenem zu allen Pointern zuweisungskompatiblem> Datentyp.
Und das würdest du nicht als "Ausnahmeregelung" bezeichnen?
> Und schon hätte Stroustrup ein verhasstes #define weniger gehabt.
Statt "hätte gehabt" muß es "wird haben" heißen. In C++0x wird es das
nämlich geben.
Dann haben wir wohl recht unterschiedliche Meinungen darüber, was eine
Ausnahme ist. Für mich ist jedenfalls ein spezielles Symbol, das als
einziges in der gesamten Sprache zu allen Zeigertypen
zuweisungskompatibel ist, ganz offensichtlich eine Ausnahme.
Rolf Magnus schrieb:> Dann haben wir wohl recht unterschiedliche Meinungen darüber, was eine> Ausnahme ist. Für mich ist jedenfalls ein spezielles Symbol, das als> einziges in der gesamten Sprache zu allen Zeigertypen> zuweisungskompatibel ist, ganz offensichtlich eine Ausnahme.
Nur ist das dann schon immer eine Ausnahme gewesen. Nur ein Integer mit
dem Wert 0 darf in einen Pointer umgewandelt werden, Integer mit anderen
Werten nicht. Nichtmal die in C noch erlaubte Umwandlung Pointer ->
Integer -> Pointer ist in C++ erlaubt, ausser es handelt sich eben um
Nullpointer.
Das neue Keyword ist also nicht mehr Ausnahme als der bisherige Stand
auch, und bietet den Vorteil, dass es nicht mehr zu solchen
Überraschungen wie oben führt, wenn Überladung von Funktionen ins Spiel
kommt.
Andreas
Andreas Ferber schrieb:> Das neue Keyword ist also nicht mehr Ausnahme als der bisherige Stand> auch, und bietet den Vorteil, dass es nicht mehr zu solchen> Überraschungen wie oben führt, wenn Überladung von Funktionen ins Spiel> kommt.
Richtig, aber ich habe auch nur der Behauptung widersprochen, daß dies
ohne Ausnahmeregelung auskäme und nicht geschrieben, daß es vorher keine
gegeben hätte.