pointer auf const

#1791552
Lesenswert?

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.
#1791564
Lesenswert?

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.
#1791572
Lesenswert?

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...
#1791582
Lesenswert?

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
typedef char* CHARS;
6
typedef const CHARS cptr_1;
7
typedef CHARS const cptr_2;
8

9
int main()
10
{
11
  cptr_1 ptr1 = "Hallo";
12
  cptr_2 ptr2 = "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
typedef char * CHARS;
2
CHARS  i, j, k, l;

alle Variablen i, j, k und l vom Datentyp Pointer to char.

während in
1
#define CHARS char *
2
CHARS i, 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.
#1791597
Lesenswert?

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.
#1791608
Lesenswert?

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
typedef struct
2
{
3
  int irgendwas;
4
  char was_anderes;
5
}
6
my_struct;
7
typedef my_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.
#1791907
Lesenswert?

Dadurch dass in der Deklaration die Qualifier, Type und Storage Class 
keine bestimmte Reihenfolge einhalten müssen, ist übrigens auch sowas 
erlaubt:
1
int typedef const foo;

"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
#1792032
Lesenswert?

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
>   int const i = 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 * const i = 0, j = 0;
2
>

links von const ist ein int*. Also werden zwei konstante Pointer auf 
(variable) ints deklariert.

> ebenso wie bei
>
1
>   int const * 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
int const * const i

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.
1
const int i=0;
#1792060
Lesenswert?

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
const int i[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
const int i1, i2;
2
const int *p1, *p2;

und /nicht/:
1
const int i1, 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
#1792156
Lesenswert?

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...
#1792188
Lesenswert?

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.
#1792211
Lesenswert?

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.
#1792539
Lesenswert?

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.
#1792546
Lesenswert?

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
return foo[i];

Das einzig relevante ist, dass bei der Addition irgendetwas rauskommt, 
was mit dem *-Operator dereferenziert werden kann.

Andreas
Moderator #1792650
Lesenswert?

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.
#1792665
Lesenswert?

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 :-)
#1792679
Lesenswert?

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
class ParamaterSet
2
{
3
public:
4

5
   void Set( const char * name, int value );
6
   void Set( const char * name, double value );
7
   void Set( const char * name, const char * value );
8
};
9

10
int main()
11
{
12
  ParameterSet params;
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
class A
2
{
3
public:
4
  void foo( int value );
5
};
6

7
class B : public A
8
{
9
public:
10
  void foo( double value );
11
};
12

13
int main()
14
{
15
  B obj;
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.
Gast #1793362
Lesenswert?

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.
Gast #1793419
Lesenswert?

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.
#1793433
Lesenswert?

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
Gast #1793571
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren