Forum: PC-Programmierung C++ Startadresse: char Array vs int Array


von Alex (Gast)


Lesenswert?

Hi

Ich bin gerade dabei, C++ zu lernen, und bin beim Thema Arrays. Die 
Startaddresse eines Arrays ist der Name des Arrays selbst. So kenne ich 
es aus C. In C++ bin ich auf etwas gestoßen, das ich mir nicht selbst 
erklären kann.
MinGW, VSCODE, W10 64-bit:
1
#include <iostream>
2
3
using namespace std;
4
5
int main()
6
{
7
    int myArr[] {10, 25, 4};
8
    char myName[] {'A', 'l', 'e', 'x'};
9
10
    cout << "Address of myArr: " << myArr << endl;      // Address of where the array myArr starts
11
    cout << "Address of myName: " << myName << endl;     // Address of where the array myName starts
12
13
    return 0;
14
}

Output:
> Address of myArr: 0x61ff04

> Address of myName: Alex

Mag mich jemand aufklären?

Danke.

von Rolf M. (rmagnus)


Lesenswert?

Alex schrieb:
> Mag mich jemand aufklären?

Wenn du einen char* per Operator << ausgibst, wird davon ausgegangen, 
dass du einen String ausgeben willst und nicht die Adresse seines ersten 
Zeichens. Oder was würdest du bei einem std::cout << "Hello world\n"; 
erwarten? Die Adresse bekommst du z.B. mit einem Cast nach void*.

: Bearbeitet durch User
von Sebastian (Gast)


Lesenswert?

cout versucht intelligent zu sein. Wenn es einen Zeiger auf char 
bekommt, gibt es den als string aus.

von g457 (Gast)


Lesenswert?

> Mag mich jemand aufklären?

Kuckst Du Operatoren. Hier:
1
    cout << "Address of myArr: " << uintptr_t(myArr) << endl;      // Address of where the array myArr starts
2
3
    cout << "Address of myName: " << uintptr_t(myName) << endl;     // Address of where the array myName starts

(..oder so ähnlich)

HTH

von Yalu X. (yalu) (Moderator)


Lesenswert?

Es ist schon etwas schräg, dass bei der Pointer-Überladung von <<
speziell für char-Pointer eine Ausnahme gemacht wird. Den Grund hat ja
Rolf schon  genannt:

Rolf M. schrieb:
> Oder was würdest du bei einem std::cout << "Hello world\n";
> erwarten?

Das ist halt – wie noch ein paar andere Dinge – historisch gewachsen. Da
man aber nur ganz selten Pointer-Werte ausgeben muss, ist das kein allzu
großes Problem.

Unnötigerweise erstreckt sich die Überladung dabei gleich über sämtliche
char-Pointer-Typen, nämlich

1
char *
2
signed char *
3
unsigned char *
4
const char *
5
const signed char *
6
const unsigned char *

Um C-Strings komfortabel ausgeben zu können, hätten eigentlich char *
und const char * genügt.

Man beachte auch, dass für die Ausgabe mit dem <<-Operator der
char-Pointer auf einen nullterminierten String zeigen muss. Eine
fehlende Nullterminierung, kann zur unerwünschten Ergebnissen führen:

1
#include <iostream>
2
3
using namespace std;
4
5
int main()
6
{
7
    int myArr[] {10, 25, 4};
8
    char myName1[] {'A', 'l', 'e', 'x'};
9
    char myName2[] {'O', 't', 't', 'o'};
10
11
    cout << "Address of myArr: " << myArr << endl;
12
    cout << "Address of myName1: " << myName1 << endl;
13
    cout << "Address of myName2: " << myName2 << endl;
14
15
    return 0;
16
17
}

Ausgabe:

1
Address of myArr: 0x7fff620b2494
2
Address of myName1: AlexOtto
3
Address of myName2: Otto

: Bearbeitet durch Moderator
von Rolf M. (rmagnus)


Lesenswert?

Yalu X. schrieb:
> Unnötigerweise erstreckt sich die Überladung dabei gleich über sämtliche
> char-Pointer-Typen, nämlich
>
> char *
> signed char *
> unsigned char *
> const char *
> const signed char *
> const unsigned char *

Nicht nur über die Zeiger, sondern auch die char-Typen selber. Das führt 
dazu, dass z.B. bei
1
uint8_t val = 100;
2
std::cout << val << '\n';
nicht etwa "100", sondern (bei ASCII-basierten Zeichensätzen) "d" 
ausgegeben wird.

von Thomas F. (tommf)


Lesenswert?

Falls das bis jetzt alles verwirrend klingt und weil der TO von C kommt: 
In C++ sollte man die richtigen Typen für seine Aufgaben verwenden. Das 
heisst in diesem Fall std::string anstatt char[]. Das Arbeiten mit 
einfachen Pointern sollte man sich abgewöhnen, damit fällt man in C++ 
genauso oft auf die Nase, wie in C.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Thomas F. schrieb:
> Das Arbeiten mit einfachen Pointern sollte man sich abgewöhnen, damit
> fällt man in C++ genauso oft auf die Nase, wie in C.

Es ist aber trotzdem kein Fehler, sie verstanden zu haben, und das schon
möglichst frühzeitig. Früher oder später kommt mal mit den Pointern
sowieso in Berührung, evtl. sogar in einem wesentlich komplexeren Umfeld
als in dem Beispiel des TE. Dann ist es von Vorteil, wenn man bereits
sicher mit ihnen umgehen kann, um eben gerade nicht auf die Nase hzu
fallen.

von Alex (Gast)


Lesenswert?

Vielen Dank euch allen. :)

Das muss ich erstmal sacken lassen, und mich weiter tiefgründig mit C++ 
beschäftigen. Irgendwann fällt der Groschen sicherlich :)

Gruß,

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

g457 schrieb:
> Kuckst Du Operatoren. Hier:

Das ist zwar rein technisch ein Operator, aber "+" ist auch ein 
Operator; präziser sollte man hier "Cast" sagen.
Solche functional casts sollte man in C++ nicht benutzen, weil sie die 
gleichen Regeln wie klassische C-Casts haben, welche "zu mächtig" sind 
und mit welchen man schnell versehentlich etwas falsch macht. Außerdem 
ist dem Leser nicht leicht ersichtlich, was da passiert. Besser sind 
daher Named Casts:
1
std::cout << "Address of myArr: " << reinterpret_cast<std::uintptr_t> (myName) << std::endl; // Address of where the array myArr starts

Dem Leser ist hier durch das reinterpret_cast sofort ersichtlich, dass 
hier etwas "böses" passiert, nämlich ein cast von Pointer nach int. Das 
ist selten nötig und fehleranfällig

Außerdem wird der Pointer dann als Dezimalzahl Ausgegeben. Sinnvoller 
ist der cast nach "void*":
1
std::cout << "Address of myArr: " << static_cast<void*> (myName) << std::endl; // Address of where the array myArr starts

Das ist dann auch ein static_cast, welcher "harmloser" ist.

Alex schrieb:
> using namespace std;

Sollte man nicht machen: https://stackoverflow.com/q/1452721

Yalu X. schrieb:
> Eine
> fehlende Nullterminierung, kann zur unerwünschten Ergebnissen führen:

Hier sollte man klarer sagen, dass es nicht einfach nur ein 
"unerwünschtes Ergebnis" ist, sondern ein buffer overflow, was undefined 
behaviour ist, d.h. ein Fehler, bei dem "irgendetwas" passieren kann, 
vom Programmabsturz bis zu gelöschten Daten, und daher unbedingt zu 
vermeiden ist.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Niklas G. schrieb:
> Alex schrieb:
>> using namespace std;
>
> Sollte man nicht machen: https://stackoverflow.com/q/1452721

Allerdings finde ich das andere Extrem, das gerne als beste Variante 
verkauft wird, nämlich überall explizit std:: davor zu schreiben, 
inzwischen auch nicht mehr so arg prickelnd.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> Allerdings finde ich das andere Extrem, das gerne als beste Variante
> verkauft wird, nämlich überall explizit std:: davor zu schreiben,
> inzwischen auch nicht mehr so arg prickelnd.

Finde ich gar nicht so schlimm. So sieht man beim Lesen sofort, wo der 
Bezeichner herkommt. Die Zeit die man zum Tippen des "std::" braucht ist 
eh praktisch 0 im Vergleich zur gesamten Entwicklungszeit. Wenn man das 
wirklich gar nicht aushält kann man ja problemlos "using std::cout;" 
o.ä. in die Funktion/Klasse/Namespace schreiben.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Anstatt sämtliche Symbole des Namespaces mit

1
using namespace std;

unqualifiziert sichtbar zu machen, tut man dies besser nur für
diejenigen Symbole, die man auch tatsächlich benötigt:

1
using std::cout, std::endl, std::hex, std::uppercase, std::setw,
2
      std::setfill;

Seit C++17 kann man wie oben gezeigt mehrere Symbole in eine Zeile
packen (vor C++17 musste man ganz umständlich für jedes Symbol eine
eigene using-Deklaration schreiben, weswegen von dieser Möglichkeit kaum
jemand Gebrauch gemacht hat).

Durch diese selektiven using-Deklarationen umgeht man zum einen das
Problem mit zukünftigen Name-Clashes bei der Verwendung von using
namespace, zum anderen muss man den Quellcode nicht mit unzähligen std::
verunstalten.

1
  cout << "0x" << hex << uppercase << setw(8) << setfill('0') << 42 << endl;

sieht eben doch etwas anständiger aus als

1
  std::cout << "0x" << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << 42 << std::endl;

Noch angenehmer wäre die obige using-Deklaration zu schreiben, wenn man
auch dort den Namespace-Namen nicht ständig wiederholen müsste. Andere
Programmiersprachen wie bspw. Python (from Modulname import ...) oder
Haskell (import Modulname (...)) zeigen, wie es geht. In C++ könnte die
entsprechende Syntax folgendermaßen aussehen:

1
using namespace std (cout, endl, hex, uppercase, setw, setfill);

Vielleicht kommt das ja in C++26 :)

: Bearbeitet durch Moderator
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.