Forum: PC-Programmierung c - Pointer auf const als Funktionsargument


von mh (Gast)


Lesenswert?

Ich habe ein kleines "Problem" mit dem const Qualifier und 
Funktionsargumenten, bei dem mir die c-Experten sicher helfen können.

Eine Funktion soll in einem string die Position des ersten Zeichens 
bestimmen, das kein Leerzeichen ist und einen Zeiger auf dieses Zeichen 
für den Benutzer zur Verfügung stellen. Der Rückgabewert steht nicht für 
den Zeiger zur Verfügung, da er für irgendeine andere wichtige 
Information benötigt wird.

funktion1 löst diese Aufgabe. Ich würde jetzt gerne die Argumentliste 
mit const Qualifiern erweitern, um anzuzeigen, dass der eigentliche 
string nicht verändert wird und damit mir der compiler dabei hilft den 
string in der Funktion nicht zu verändern. Ich frage mich allerdings wie 
ich das mache, ohne dabei einen cast anzuwenden. Ich habe verschiedene 
Versionen und die entsprechenden Warnungen des compilers angegeben.

Mir ist klar warum es diese Warnungen gibt und mir ist auch klar, dass 
char const** nicht sehr sinnvoll ist.
1
#include <stdlib.h>
2
#include <stdio.h>
3
#include <string.h>
4
5
void funktion1(char* start, char** ende){
6
    while(*start && *start == ' '){
7
        start++;
8
    }
9
    *ende = start;
10
}
11
12
void funktion2(char const* start, char const** ende){
13
    while(*start && *start == ' '){
14
        start++;
15
    }
16
    *ende = start;
17
}
18
19
void funktion3(char const* start, char** ende){
20
    while(*start && *start == ' '){
21
        start++;
22
    }
23
    *ende = start; // warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
24
}
25
26
void funktion4(char const* start, char** ende){
27
    while(*start && *start == ' '){
28
        start++;
29
    }
30
    *ende = (char*)start;
31
}
32
33
char const* funktion5(char const* start){
34
    while(*start && *start == ' '){
35
        start++;
36
    }
37
    return start;
38
}
39
40
int main(){
41
    char const* test = "  Hallo Welt!";
42
    char const* ende;
43
44
    funktion1(test, &ende); // warning: passing arguments 1 and 2 of ‘funktion1’ discards ‘const’ qualifier from pointer target types [-Wdiscarded-qualifiers]
45
    funktion2(test, &ende);
46
    funktion3(test, &ende); // warning: passing argument 2 of ‘funktion3’ from incompatible pointer type [-Wincompatible-pointer-types]
47
    funktion4(test, &ende); // warning: passing argument 2 of ‘funktion4’ from incompatible pointer type [-Wincompatible-pointer-types]
48
    ende = funktion5(test);
49
50
    char test2[20];
51
    char *ende2;
52
    strcpy(test2, test);
53
54
    funktion1(test2, &ende2);
55
    funktion2(test2, &ende2); // warning: passing argument 2 of ‘funktion2’ from incompatible pointer type [-Wincompatible-pointer-types]
56
    funktion3(test2, &ende2);
57
    funktion4(test2, &ende2);
58
    ende2 = funktion5(test2); // warning: assignment makes integer from pointer without a cast [-Wint-conversion]
59
}

Alle Varianten haben entweder beim Aufruf mit const oder nicht const 
Argumenten Probleme.

Ein Beispiel aus der Standardbibliothek mit ähnlichen Problemen ist 
strtod.
1
double strtod(char const* str, char** str_end);
Eine Implementation die ich dazu gefunden habe benötigt intern einen 
cast von const char* nach char*, um str_end zu erhalten.

Gibt es eine Möglichkeit ohne cast auszukommen, oder das Problem elegant 
zu umgehen?

Vielen Dank fürs Lesen und alle konstruktiven Antworten!

von start (Gast)


Lesenswert?

mh schrieb:
> while(*start && *start == ' ')

Die Prüfung, ob *start ungleich 0 ist, ist überflüssig.

von Mikro 7. (mikro77)


Lesenswert?

mh schrieb:
> Gibt es eine Möglichkeit ohne cast auszukommen, oder das Problem elegant
> zu umgehen?

Wüßte auf Anhieb auch nicht wie. Entweder mit cast (funktion4) oder zwei 
separate Funktionen (1 und 2).

Häufig reicht allerdings ein offset statt eines pointers. Dann bietet 
sich an:
1
void funktion(char const* start, size_t *offset) ;

von Daniel (Gast)


Lesenswert?

Wow, schon zwei Antworten vor mir und keine davon sagt, dass funktion2 
der richtige Ansatz ist.

Diesen Link sollte man sich für die richtig komplizierten Fälle merken: 
http://cdecl.org/
Gibt es auch als Programm für die Konsole.

Btw., sehr wenige Leute schreiben char const statt const char.

von Daniel A. (daniel-a)


Lesenswert?

Man muss eben die richtigen Teile auf const setzen. Ich habe const noch 
nie an die Stelle gesetzt an der es bei dir steht, worauf bezieht es 
sich dort?

Ich würde folgendes Probieren: (ungetestet)
1
void funktion1(const char* start, const char**const ende){
2
    while(*start && *start == ' '){
3
        start++;
4
    }
5
    *ende = start;
6
}
7
8
int main(){
9
    const char* test = "  Hallo Welt!";
10
    const char* ende = 0;
11
...

von mh (Gast)


Lesenswert?

start schrieb:
> Die Prüfung, ob *start ungleich 0 ist, ist überflüssig.

Da hast du natürlich recht.

Mikro 7. schrieb:
> Häufig reicht allerdings ein offset statt eines pointers. Dann bietet
> sich an:
> void funktion(char const* start, size_t *offset) ;

Das ist vermutlich in diesem Fall die beste Lösung. Da hätte ich mir 
vielleicht etwas mehr Mühe geben müssen, um ein Beispiel zu konstruieren 
bei dem man zwingend einen Zeiger von der Funktion benötigt.

Daniel schrieb:
> Wow, schon zwei Antworten vor mir und keine davon sagt, dass funktion2
> der richtige Ansatz ist.

Inwiefern ist funktion2 der "richtige" Ansatz? Er liefert offensichtlich 
Warnungen beim zweiten Aufruf.

Daniel A. schrieb:
> Man muss eben die richtigen Teile auf const setzen. Ich habe const noch
> nie an die Stelle gesetzt an der es bei dir steht, worauf bezieht es
> sich dort?

const char und char const ist identisch.

> const char**const ende

Das zweite const wäre meiner Meinung nach für das Beispiel unwirksam. Es 
verhindert, dass man ende in der Funktion verändern darf.
Wenn in der Funktion steht:
1
ende++;
müsste der compiler meckern.

von Rolf M. (rmagnus)


Lesenswert?

Daniel A. schrieb:
> Man muss eben die richtigen Teile auf const setzen. Ich habe const noch
> nie an die Stelle gesetzt an der es bei dir steht, worauf bezieht es
> sich dort?

const bezieht sich immer auf das, was direkt links davon steht, mit 
einer Ausnahme: Wenn es ganz links steht, bezieht es sich stattdessen 
auf das, was rechts davon steht. Die meisten Leute kennen lustigerweise 
nur diese Ausnahme, aber nicht die Regel.

von Josef (Gast)


Lesenswert?

mh schrieb:
> Mir ist klar warum es diese Warnungen gibt und mir ist auch klar, dass
> char const** nicht sehr sinnvoll ist.

Wie kommst du darauf das char const** nicht sinnvoll ist.

Wenn der String nicht veraenderbar sein soll muss der Rueckgabetyp
const char** sein.
Falls du char* zurueckgibst ist der String ueber diesen Pointer 
veraenderbar.

Funktion2 ist korrekt und die Warnung beim 2. Aufruf ist auch voellig
korrekt, da dort eine Zuweisung von const char* auf char* auftritt.

von Mikro 7. (mikro77)


Lesenswert?

Josef schrieb:

> Wie kommst du darauf das char const** nicht sinnvoll ist.

Es ist "unpraktisch". Zumindest, wenn man nur diese eine Funktion zur 
Verfügung stellt.

http://stackoverflow.com/questions/3874196/why-is-the-endptr-parameter-to-strtof-and-strtod-a-pointer-to-a-non-const-char-p

von Rolf M. (rmagnus)


Lesenswert?

Josef schrieb:
> mh schrieb:
>> Mir ist klar warum es diese Warnungen gibt und mir ist auch klar, dass
>> char const** nicht sehr sinnvoll ist.
>
> Wie kommst du darauf das char const** nicht sinnvoll ist.

Weil eben eine Warnung kommt, wenn man die Adresse eines Zeigers auf 
(non-const) char übergibt.

> Wenn der String nicht veraenderbar sein soll muss der Rueckgabetyp
> const char** sein.
> Falls du char* zurueckgibst ist der String ueber diesen Pointer
> veraenderbar.
>
> Funktion2 ist korrekt und die Warnung beim 2. Aufruf ist auch voellig
> korrekt, da dort eine Zuweisung von const char* auf char* auftritt.

Ich sehe dort nirgends eine solche Zuweisung.
Die Warnung kommt, weil char* und const char* unterschiedliche Typen 
sind. Zeiger auf diese beiden Zeigertypen sind daher nicht ohne Cast 
zuweisungskompatibel.

von Josef (Gast)


Lesenswert?

Rolf M. schrieb:
> Josef schrieb:
>> mh schrieb:
>>> Mir ist klar warum es diese Warnungen gibt und mir ist auch klar, dass
>>> char const** nicht sehr sinnvoll ist.
>>
>> Wie kommst du darauf das char const** nicht sinnvoll ist.
>
> Weil eben eine Warnung kommt, wenn man die Adresse eines Zeigers auf
> (non-const) char übergibt.
>

Er will einen char const** benutzen, damit der String nicht aenderbar 
ist.
Wegen der Warnung ist das nicht sinnvoll?

>> Wenn der String nicht veraenderbar sein soll muss der Rueckgabetyp
>> const char** sein.
>> Falls du char* zurueckgibst ist der String ueber diesen Pointer
>> veraenderbar.
>>
>> Funktion2 ist korrekt und die Warnung beim 2. Aufruf ist auch voellig
>> korrekt, da dort eine Zuweisung von const char* auf char* auftritt.
>
> Ich sehe dort nirgends eine solche Zuweisung.
> Die Warnung kommt, weil char* und const char* unterschiedliche Typen
> sind. Zeiger auf diese beiden Zeigertypen sind daher nicht ohne Cast
> zuweisungskompatibel.

Die Warnung kommt daher weil die Ausdruecke nicht zuweisungskompatibel 
sind. Aber wenn da keine Zuweisung ist, dann gibt es auch keine Warnung.
Problem geloest.

Ich glaub ich bin im falschen Film.

von Rolf M. (rmagnus)


Lesenswert?

Josef schrieb:
> Er will einen char const** benutzen, damit der String nicht aenderbar
> ist.
> Wegen der Warnung ist das nicht sinnvoll?

Naja, sagen wir so: Es gibt keine Möglichkeit, das universell zu machen, 
so dass man da keinen Cast braucht. Es kann eben auch sein, dass er 
einen Zeiger auf non-const char hat und dessen Adresse übergeben will. 
Das geht dann aber nicht ohne Cast.
Auf das Wesentliche runtergebrochen:
Bei einer Funktion
1
void foo(const char* p)
kann man auch einen Zeiger auf non-const übergeben, ohne dass das ein 
Problem wäre. Aber bei einer Funktion
1
void foo(const char** p)
gibt es die Warnung, wenn man Zeiger auf einen Zeiger auf non-const 
übergibt. Und das ist das Problem, und das hat nichts mit einer Zuweisug 
eines const char* an einen char* zu tun.

> Die Warnung kommt daher weil die Ausdruecke nicht zuweisungskompatibel
> sind. Aber wenn da keine Zuweisung ist, dann gibt es auch keine Warnung.
> Problem geloest.
>
> Ich glaub ich bin im falschen Film.

Nur weil du's nicht verstanden hast, musst du dich nicht über andere 
lustig machen. Aber damit du nicht dumm stirbst: 
Zuweisungskompatibilität ist nicht nur für Zuweisungen wichtig.
Fakt ist: Bei der Übergabe gibt es beim Aufruf der funktion2 nirgends 
eine Zuweisung eines const char* an einen char*. Hier mal der relevante 
Teil:
1
void funktion2(char const* start, char const** ende){
2
3
//...
4
5
    char test2[20];
6
    char *ende2;
7
    strcpy(test2, test);
8
 
9
    funktion2(test2, &ende2); // warning: passing argument 2 of ‘funktion2’ from incompatible pointer type [-Wincompatible-pointer-types]
Und nun sag mir bitte, wie hier die Zuweisung eines const char* an einen 
char* Grund für diese Warnung sein soll.

von Daniel A. (daniel-a)


Lesenswert?

mh schrieb:
> Gibt es eine Möglichkeit ohne cast auszukommen, oder das Problem elegant
> zu umgehen?

Es ist vielleicht nicht Elegant, aber man könnte mehrere Funktionen 
erstellen. Mit C11 Generics kann man noch soetwas wie Überladung 
erzeugen.
1
#include<stddef.h>
2
3
void funktion1_size_t(const char* start, size_t* offset){
4
  const char* it = start;
5
  while( *it == ' ' )
6
    it++;
7
  *offset = it - start;
8
}
9
10
void funktion1_char( char* start, char** end ){
11
  size_t offset;
12
  funktion1_size_t( start, &offset );
13
  *end = start + offset;
14
}
15
16
void funktion1_const_char( const char* start, const char** end ){
17
  size_t offset;
18
  funktion1_size_t( start, &offset );
19
  *end = start + offset;
20
}
21
22
#define funktion1( a, b ) _Generic( (b), \
23
    const char**: funktion1_const_char, \
24
    char**: funktion1_char, \
25
    size_t*: funktion1_size_t \
26
  )( (a), (b) )
27
28
int main(){
29
  {
30
    const char* test = "  Hallo Welt!";
31
    const char* ende;
32
    funktion1( test, &ende );
33
  }
34
  {
35
    char test[] = "  Hallo Welt!";
36
    char* ende;
37
    funktion1( test, &ende );
38
  }
39
  {
40
    char test[] = "  Hallo Welt!";
41
    size_t offset;
42
    funktion1( test, &offset );
43
  }
44
}

von mh (Gast)


Lesenswert?

Daniel A. schrieb:
> Es ist vielleicht nicht Elegant, aber man könnte mehrere Funktionen
> erstellen. Mit C11 Generics kann man noch soetwas wie Überladung
> erzeugen.

Generics sehen interessant aus und ich habe sie auf meine TODO Liste 
gesetzt. Allerdings hab ich auf die Schnelle gefunden, dass die Nutzung 
(noch?) compilerabhängig ist. 
http://en.cppreference.com/w/c/language/generic

von Daniel A. (daniel-a)


Lesenswert?

mh schrieb:
> Allerdings hab ich auf die Schnelle gefunden, dass die Nutzung
> (noch?) compilerabhängig ist

Es steht im C11 Standard, somit ist es nicht compilerabhängig. Der 
Compiler muss jedoch den C11 Standard vollständig unterstützen. Bei GCC 
kann man den c11 Standard mit dem Parameter --std=c11 aktivieren. Bei 
clang müsste das genauso gehen. MSVC hängt glaub ich immernoch irgendwo 
zwischen C90/C98 herum...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.