PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Frage zu Segmentation fault (Unterschied zwischen Array und Zeiger)



BLUESCREEN3D
04-02-2006, 18:19
Folgendes Programm:

#include <stdio.h>

int main()
{
char bla1[4]="bla";
printf("bla1...\n");
*bla1=0; //geht

char *bla2="bla";
printf("bla2...\n");
*bla2=0; //geht nicht
}
Dabei geht *bla1=0 ohne Probleme, aber *bla2=0 gibt ein Segmentation fault.

Ich vermute, dass die Daten in bla2 irgendwie auf readonly gesetzt sind oder so...

Aber warum wird zwischen bla1 und bla2 überhaupt ein Unterschied gemacht? Eigentlich sind Arrays und Zeiger intern doch das gleiche!?
Wird davon ausgegangen, dass man einen so definierten String nie ändern will?

bischi
04-02-2006, 18:36
Wundert mich sowieso, dass

char a=0;

einfach so geht; Mach doch zumindest ' ' drumherum...

Und afaik werden doch Pointern die Werte mittels Adress-operator & zugewiesen...

MfG Bischi

locus vivendi
04-02-2006, 19:06
Ich vermute, dass die Daten in bla2 irgendwie auf readonly gesetzt sind oder so...
Da sollte man nicht vermuten. In eine Stringliteral darf man nicht schreiben. Deshalb solltest du auch nicht <<char* p = "...";>> machen, sondern <<char const* p = "...";>>. Dann macht dich der Compiler auf den Fehler aufmerksam.


Aber warum wird zwischen bla1 und bla2 überhaupt ein Unterschied gemacht? Eigentlich sind Arrays und Zeiger intern doch das gleiche!?
Ein recht verbreiteter Irtum. Nein, Zeiger und Arrays sind nicht das gleiche. Aber deshalb stürzt dein Programm nicht ab. bla1 und bla2 unterscheiden sich in der Beziehung gar nicht, auch wenn du das glaubst. Beide sind Zeiger, und beide Zeigen auf ein Array. Der Unterschied ist bloß, das bla2 auf eine Array zeigt, welches du nicht verändern darfst.

bischi:
Bei <<char a=0;>> wird einfach eine integrale Konvertierung vorgenommen.

BLUESCREEN3D
04-02-2006, 22:27
In eine Stringliteral darf man nicht schreiben. (...) beide Zeigen auf ein Array. Der Unterschied ist bloß, das bla2 auf eine Array zeigt, welches du nicht verändern darfst.
Angenommen ich will etwas äquivalentes zu dem Stringliteral haben, was ich aber zur Laufzeit noch verändern kann - wie setzt man das dann üblicherweise um?
Nimmt man dann ein Array statt einem Zeiger oder bleibt man lieber beim Zeiger und allokiert dann erst zur Laufzeit den Speicher und kopiert den Inhalt eines Stringliterals da rein? Oder gibt es noch andere Lösungen?

peschmae
05-02-2006, 11:10
Na das kommt drauf an was du benötigst. Wenn du die grösse (oder eine vernünftige Maximalgrösse) schon vorher kennst nimmst du ein Array.
Sonst musst du halt wohl oder übel mit malloc drauf los...

MfG Peschmä

bischi
05-02-2006, 12:08
Oder std::vector<char> verwenden. Kannst du beliebig viele Zeichen anhängen und entfernen.

Geht natürlich nur in C++; Für C würd ich irgend eine Linked-List verwenden.



bischi:
Bei <<char a=0;>> wird einfach eine integrale Konvertierung vorgenommen.
Klar - aber guter Programmierstil ist das nicht gerade...



MfG Bischi

peschmae
05-02-2006, 12:21
Und wie wäre denn die "gute Stilvariante" von "char a = 0"? Ascii-Tabelle nachgucken was jetzt 0 schon wieder war? Naja, *das* ist dann auch gut leserlicher Code ;)

Und eine linked List für Chars ist ja wohl eher ein schlechter Witz - ich meine da benutzt du etwa 4 mal mehr Speicher für Pointer als für die Daten... :D

MfG Peschmä

Joghurt
05-02-2006, 16:32
Ja, schade, dass man nicht mehr in Strings schreiben kann, wie noch vor 10 Jahren ;)

Denn dort konnte man so schöne sachen machen:

#include <stdio.h>
int main()
{
"Sie nutzen C++"[12] = 0;
printf("Sie nutzen C++");
}Ein mit einem C-Compiler compilierte Code würde "Sie nutzen C" ausgeben, mit C++ compiliert "Sie nutzen C++"
Was heute immer noch geht, ist
int main()
{
int a=4;
int b[10];

a[b] = 12;
}

ninguno
05-02-2006, 17:52
Was heute immer noch geht, ist
int main()
{
int a=4;
int b[10];

a[b] = 12;
}
eine variante davon ist auch
int a[2];
a[0]=5;
1[a]=6;
diese dinge funktionieren wahrscheinlich wegen der assoziativität der (pointer) addition

BLUESCREEN3D
06-02-2006, 00:53
Oder std::vector<char> verwenden. Kannst du beliebig viele Zeichen anhängen und entfernen.
Ums Anhängen ging es mir ja garnicht, sondern nur darum, einen vorbelegten Speicherbereich verändern zu können.

Und wegen dem char a=0: In dem Beispiel aus dem ersten Post habe ich den String so verändert, dass er anschließend leer ist. Also war nicht '0', sondern wirklich 0 gemeint.

Joghurt
06-02-2006, 01:57
Ums Anhängen ging es mir ja garnicht, sondern nur darum, einen vorbelegten Speicherbereich verändern zu können.Dann schreib doch
char a[]="bla"

BLUESCREEN3D
06-02-2006, 03:38
Dazu hatte peschmae mir ja schon geraten :D

(Von meiner Seite hat sich der Thread erledigt.)

bischi
06-02-2006, 14:00
Ums Anhängen ging es mir ja garnicht, sondern nur darum, einen vorbelegten Speicherbereich verändern zu können.

Ja, aber wenn du anhängen und ändern kannst, kannst du beliebig grosse Texte später reinschreiben und editieren.



Und wegen dem char a=0: In dem Beispiel aus dem ersten Post habe ich den String so verändert, dass er anschließend leer ist. Also war nicht '0', sondern wirklich 0 gemeint.

Ich schreib da jeweils char a = null;

MfG Bischi

peschmae
06-02-2006, 16:56
Und null ist was?

Ich finde 0 eigentlich genug klar ;)

MfG Peschmä

quinte17
06-02-2006, 17:14
NULL ist nicht definiert :P ist weder 0 noch sonst irgendwas.
somit wenn man NULL meint sollte man auch NULL schreiben :D

greetz

awehrm
06-02-2006, 18:17
Natürlich ist "NULL" definiert, sonst könntest du es ja gar nicht verwenden.
Ist in irgendeiner Header mithilfe der Präprozessor Direktiven definiert worden.
Wenn ich mir den Wert von NULL ausgeben lasse erscheint 0, also denke
ich mal das die Symbolische-Konstante NULL den dezimalen Wert 0 hat.

peschmae
06-02-2006, 18:31
Ach jetzt heissts plötzlich NULL. Ein kleines Chamäleon haben wir da :D

Na, egal, ich halts mit Stroustrup und find NULL blöd. :p

MfG Peschmä

BLUESCREEN3D
06-02-2006, 19:03
Ich schreib da jeweils char a = null;
Ich vermute du meinst NULL?

Das bevorzuge ich eigentlich auch, aber manchmal ist das als Zeiger definiert und dann kriegt man eine Warnung bei der Zuweisung...

guardian
07-02-2006, 10:25
BTW hier (http://www2.informatik.uni-wuerzburg.de/dclc-faq/kap1.html) (FAQ in de.comp.lang.c) ist eine recht interessant FAQ bezüglich der Null- bzw. NULL-Zeiger.

Bezieht sich zwar nicht auf den aktuellen FinalDraft bzw. Standard (welchen ich moment nicht zur Hand hab) aber trotzdem ganz gut :)

locus vivendi
11-03-2008, 18:12
Aber warum wird zwischen bla1 und bla2 überhaupt ein Unterschied gemacht? Eigentlich sind Arrays und Zeiger intern doch das gleiche!?
Ein recht verbreiteter Irtum. Nein, Zeiger und Arrays sind nicht das gleiche. Aber deshalb stürzt dein Programm nicht ab. bla1 und bla2 unterscheiden sich in der Beziehung gar nicht, auch wenn du das glaubst. Beide sind Zeiger, und beide Zeigen auf ein Array. Der Unterschied ist bloß, das bla2 auf eine Array zeigt, welches du nicht verändern darfst.
Keine Ahnung warum ich das damals geschrieben habe. Im zweiten Satz schreibe ich korrekt, das Zeiger und Arrays nicht das gleiche sind. Zwei Sätze weiter widerspreche ich mir dann quasi selber, und schreibe fälschlicherweise das "bla1" und "bla2" aus dem Ursprungspost beides Zeiger wären. Richtig ist natürlich das "bla1" tatsächlich ein Array ist, wie Bluescreen3d auch angedeuet hat. Hingegen ist "bla2" tatsächlich ein Zeiger.

jan61
11-03-2008, 19:12
Moin,


Wundert mich sowieso, dass

char a=0;

einfach so geht; Mach doch zumindest ' ' drumherum...

Selbstverständlich geht das. char ist nichts weiter als ein 8-Bit-Wert, warum soll man dem nicht 0 zuweisen können? Alle numerischen Werte von -127...127 gehen. '0' ist was anderes, das ist das Gleiche wie char a=48; Dem char a=0; würde in "Character"-Schreibweise ein char a='\0'; entsprechen, aber das ist nur eine andere Schreibweise. Übrigens: char a=NULL; ist nicht zwingend identisch dazu.

Jan

ContainerDriver
11-03-2008, 22:07
Da sollte man nicht vermuten. In eine Stringliteral darf man nicht schreiben.

Hab die letzten paar Tage das selbe Problem gehabt. Wieso darf man das nicht? Weil es der Standard so vorschreibt oder hat das einen praktischen Nutzen?


Gruß, Florian

almoeli
12-03-2008, 11:03
Hi,

der praktische Nutzen ist der, dass der Compiler einen Stringliteral fest mit in den Sourcecode einbetten kann. Damit gehört der Stringliteral zum eigentlichen Programmcode.
Arrays werden erst zur Laufzeit auf dem Heap angelegt und gehören damit zu den Programmdaten, welche zur Laufzeit entstehen und damit auch verändert werden können.
Man versuchtst also mit dem Pointer auf ein Stringliteral außerhalb des Heaps des Programms zu schreiben. Da moderne Systeme das verändern des eigentlichen Programmcodes zur Laufzeit verhindern gibt es dann einen Segmentation Fault.

Gruß

almoeli

BLUESCREEN3D
12-03-2008, 14:35
Wieso darf man das nicht? Weil es der Standard so vorschreibt oder hat das einen praktischen Nutzen?
Der praktische Nutzen, dass es überhaupt read-only-Bereiche im virtuellen Speicher gibt, sind Sicherheit und z.B. die Möglichkeit, dass bei einem fork() (http://en.wikipedia.org/wiki/Fork_(operating_system)) der neu entstehende Prozess den alten Speicherbereich mit dem Code (http://en.wikipedia.org/wiki/Code_segment) mitnutzen kann (mittels Copy-On-Write (http://de.wikipedia.org/wiki/Copy-On-Write) kann auch der restliche Speicher erstmal gemeinsam genutzt werden).


Arrays werden erst zur Laufzeit auf dem Heap angelegt und gehören damit zu den Programmdaten, welche zur Laufzeit entstehen und damit auch verändert werden können.
Das kann so nicht ganz stimmen, denn irgendwo muss der Inhalt des Arrays ja auch her kommen.
Hier steht auch, dass die Daten nicht im Heap liegen: http://en.wikipedia.org/wiki/Data_segment

Warum in C/C++ gerade festgelegt wurde, dass char *bla = "text" in den read-only-Bereich kommt und char bla[] = "text" nicht, wüsste ich auch gerne :D
edit: Liegt wohl daran, dass man wegen der Möglichkeit, Arrays ohne Inhalt definieren zu können, einfach erwartet, dass der Inhalt eines Arrays im Normalfall geändert werden kann. Und die Array-Schreibweise ist ja nur eine Abkürzung für char bla[] = {'t', 'e', 'x', 't', 0}. Insofern ist das eben ein Sonderfall, während man beim Zeiger ein ganz normales String-Literal hat, wie es auch in printf("%d", a) auftauchen kann und da ist es schon besser, wenn das read-only ist.

ContainerDriver
12-03-2008, 22:50
Der praktische Nutzen, dass es überhaupt read-only-Bereiche im virtuellen Speicher gibt, sind Sicherheit und z.B. die Möglichkeit, dass bei einem fork() (http://en.wikipedia.org/wiki/Fork_(operating_system)) der neu entstehende Prozess den alten Speicherbereich mit dem Code (http://en.wikipedia.org/wiki/Code_segment) mitnutzen kann (mittels Copy-On-Write (http://de.wikipedia.org/wiki/Copy-On-Write) kann auch der restliche Speicher erstmal gemeinsam genutzt werden).


Hab dazu etwas in Linux/Unix-Systemprogrammierung von Helmut Herold gefunden, das Buch habe ich aber jetzt nicht hier. Da sind Beispiele für Variablen angegeben, die im Data-Segment landen, und da war neben globalen Variablen auch ein char* zu finden.

BLUESCREEN3D
14-03-2008, 16:45
Ich habe in meinem Programm aus dem 1. Post einfach mal beide Strings durch unterschiedlichen Text ersetzt, das ganze ohne Optimierungen kompiliert und objdump -s a.out aufgerufen.
Da sieht man dann, dass beide Strings in der Sektion .rodata landen. Also weder in .text noch in .data.

Demnach müsste der Arrayinhalt beim Programmstart doch nochmal in einen anderen Speicherbereich kopiert werden, um schreibbar zu sein. Dazu habe ich ein Programm mit einem 1-MB-String einmal als char* und einmal als char[] gestartet und die char[]-Version braucht tatsächlich zur Laufzeit 1 MB mehr Speicher.

ContainerDriver
15-03-2008, 13:59
Demnach müsste der Arrayinhalt beim Programmstart doch nochmal in einen anderen Speicherbereich kopiert werden, um schreibbar zu sein. Dazu habe ich ein Programm mit einem 1-MB-String einmal als char* und einmal als char[] gestartet und die char[]-Version braucht tatsächlich zur Laufzeit 1 MB mehr Speicher.

Genau, so ist es:
seg.c:


#include <stdio.h>

int main(void)
{
char*s1="Hallo Welt 1";
char s2[]="Hallo Welt 2";

*s1='B';
s2[0]='B';
}


objdump -s seg


Contents of section .rodata:
8048458 03000000 01000200 48616c6c 6f205765 ........Hallo We
8048468 6c742031 0048616c 6c6f2057 656c7420 lt 1.Hallo Welt
8048478 3200 2.


Zu seg gehöriger Assemblercode:

(gdb) disassemble main
Dump of assembler code for function main:
0x08048344 <main+0>: lea ecx,[esp+0x4]
0x08048348 <main+4>: and esp,0xfffffff0
0x0804834b <main+7>: push DWORD PTR [ecx-0x4]
0x0804834e <main+10>: push ebp
0x0804834f <main+11>: mov ebp,esp
0x08048351 <main+13>: push ecx
0x08048352 <main+14>: sub esp,0x20
0x08048355 <main+17>: mov DWORD PTR [ebp-0x8],0x8048460
0x0804835c <main+24>: mov eax,ds:0x804846d
0x08048361 <main+29>: mov DWORD PTR [ebp-0x15],eax
0x08048364 <main+32>: mov eax,ds:0x8048471
0x08048369 <main+37>: mov DWORD PTR [ebp-0x11],eax
0x0804836c <main+40>: mov eax,ds:0x8048475
0x08048371 <main+45>: mov DWORD PTR [ebp-0xd],eax
0x08048374 <main+48>: movzx eax,BYTE PTR ds:0x8048479
0x0804837b <main+55>: mov BYTE PTR [ebp-0x9],al
0x0804837e <main+58>: mov eax,DWORD PTR [ebp-0x8]
0x08048381 <main+61>: mov BYTE PTR [eax],0x42
0x08048384 <main+64>: mov BYTE PTR [ebp-0x15],0x42
0x08048388 <main+68>: add esp,0x20
0x0804838b <main+71>: pop ecx
0x0804838c <main+72>: pop ebp
0x0804838d <main+73>: lea esp,[ecx-0x4]
0x08048390 <main+76>: ret
End of assembler dump.
(gdb) .

In 0x08048355 <main+17> wird die Speicheradresse von "Hallo Welt 1" (besser gesagt von "H") in .rodata auf dem Stack abgelegt. Dann wird von 0x0804835c <main+24> bis 0x08048371 <main+45> der Inhalt von 0x804846d bis 0x8048478 (= 12 Byte, "Hallo Welt 2" = 12 Byte) aus .rodata kopiert, bei 0x08048374 <main+48> bis 0x0804837b <main+55> kommt dann vermutlich das Nullbyte auf den Stack.
Schließlich wird in 0x0804837e <main+58> die Speicheradresse des Hs von "Hallo Welt 1" in das A Register kopiert (diese Speicheradresse wurde ja vorher bei ebp-0x8 auf den Stack abgelegt). Bei 0x08048381 <main+61> kommt es dann zum Speicherzugriffsfehler, weil versucht wird, an die Speicheradresse, die in EAX steht, etwas zu schreiben.
0x08048384 <main+64> geht, weil da nur der Stack verändert wird.

Gruß, Florian