Forum: Mikrocontroller und Digitale Elektronik 2D Char Array an Funktion übergeben


von Johny_D (Gast)


Lesenswert?

Moing

In meinem Main habe ich einen Char-Array der von einer Funktion 
verändert werden soll. Ich nehme schwer an dass ich seit gut 5 Stunden 
auf einem total falschen Weg bin und vorlauter Bäume den Wald nicht mehr 
sehe.

Gibt es eine "einfachere" und "sauberere" Lösung als das?

Die Aufgerufene Funktion
1
void Test_Function(void *Test_Char, int Text_Lenght)
2
{
3
   *(((char *)Test_Char) + (Text_Lenght * 5) + 3) = 'A';
4
   *(((char *)Test_Char) + (Text_Lenght * 9) + 5) = 'B';
5
   *(((char *)Test_Char) + (Text_Lenght * 7) + 8) = 'C';
6
   *(((char *)Test_Char) + (Text_Lenght * 8) + 10) = 'D';
7
  
8
}

Mein Test-Programm
1
#define  Text_lenght 20
2
3
char Test_Char[Text_lenght][20];
4
5
6
int main(void)
7
{
8
  
9
  Test_Function(Test_Char, Text_lenght);
10
}

Gibt es eine Möglichkeit diese Referenz in der Funktion wieder "normal" 
über [x][y] adressieren zu können?

Gibt es eine Möglichkeit die Text_lenght (also die grösse der ersten 
Dimension des Array) nicht mitgeben zu müssen?

Besten Dank für jeden Hinweis.

von Michael .. (gismi)


Lesenswert?

1
void Test_Function(char **Test_Char, int Text_Lenght)
2
{
3
  Test_Char[x][y] = 'B';
4
}
5
6
#define  Text_lenght 20
7
8
char Test_Char[20][Text_lenght];
9
10
11
int main(void)
12
{
13
  
14
  Test_Function(Test_Char, Text_lenght);
15
}

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Michael .. schrieb:
>
1
void Test_Function(char **Test_Char, int Text_Lenght)
2
> {
3
>   Test_Char[x][y] = 'B';
4
> }
5
>

Interessant, wie das funktionieren soll...

Ich habe Dein Mini-Schnipsel-Programm mal etwas erweitert, damit man es 
überhaupt einigermaßen durch einen Compiler bekommt:
1
#include <stdio.h>
2
#include <string.h>
3
4
void Test_Function(char **Test_Char, int Text_Length, int x, int y)
5
{
6
  Test_Char[x][y] = 'B';
7
}
8
9
#define  Text_length 20
10
11
char Test_Char[20][Text_length];
12
13
14
int main(void)
15
{
16
    strcpy (Test_Char[0], "Hello");
17
    strcpy (Test_Char[1], "World");
18
19
    printf ("%s\n", Test_Char[1]);
20
21
    Test_Function(Test_Char, Text_length, 1, 1);
22
    printf ("%s\n", Test_Char[1]);
23
    return 0;
24
}

Compilieren:
1
$ cc array.c -o array
2
array.c: In function 'main':
3
array.c:21:5: warning: passing argument 1 of 'Test_Function' from incompatible pointer type [enabled by default]
4
array.c:4:6: note: expected 'char **' but argument is of type 'char (*)[20]'

Ausführen:
1
$ ./array
2
World
3
Segmentation fault

Spätestens jetzt solltest Du überlegen, wie Arrays im RAM organisiert 
sind.

P.S.
Ich habe mir mal erlaubt, "Lenght" durch "Length" zu ersetzen.

Und so gehts:
1
#include <stdio.h>
2
#include <string.h>
3
4
#define  TEXT_LENGTH 20
5
6
void Test_Function(char Test_Char[][TEXT_LENGTH], int x, int y)
7
{
8
  Test_Char[x][y] = 'B';
9
}
10
11
char Test_Char[20][TEXT_LENGTH];
12
13
int main(void)
14
{
15
    strcpy (Test_Char[0], "Hello");
16
    strcpy (Test_Char[1], "World");
17
18
    printf ("%s\n", Test_Char[1]);
19
20
    Test_Function(Test_Char, 1, 1);
21
    printf ("%s\n", Test_Char[1]);
22
    return 0;
23
}

Übersetzen und Ausführen:
1
$ cc array.c -o array && ./array
2
World
3
WBrld

Warum rotzen hier manche einfach ungetesteten Code hin?

: Bearbeitet durch Moderator
von Johny_D (Gast)


Lesenswert?

Frank M. schrieb:
> Michael .. schrieb:
>>void Test_Function(char **Test_Char, int Text_Lenght)
>> {
>>   Test_Char[x][y] = 'B';
>> }
>>
> Interessant, wie das funktionieren soll...

Weiß jetzt nicht ob mich das beruhigen soll, aber diese "Lösung" hatte 
ich auch schon mal gehabt heute...

Frank M. schrieb:
> Ich habe mir mal erlaubt, "Lenght" durch "Length" zu ersetzen.

Na wenn schon auf dem Holzweg dann bitte richtig. Danke für den Hinweis.

Frank M. schrieb:
> Und so gehts:#include <stdio.h>
> #include <string.h>
>
> #define  TEXT_LENGTH 20
>
> void Test_Function(char Test_Char[][TEXT_LENGTH], int x, int y)
> {
>   Test_Char[x][y] = 'B';
> }

Macht jedenfalls was es soll. Besten Dank Frank!

Sind meine beiden "Erkenntnisse" daraus richtig? (Aus Fehlern lernen ist 
schön, aber für heute habe ich mehr gelernt als mit lieb wäre)

1.) Arrays werden in C/C++ eh immer als Referenz und nicht Kopie 
übergeben. Daher ist eine Übergabe mit Pointer eh unnötig.

2.) Es muss nur die 2te Dimension vom Array spezifiziert werden, da die 
Daten wie folgt im speicher liegen
arr[0][0]
arr[0][1]
arr[0][2]
arr[1][0]
arr[1][1]

daher spielt es keine Rolle wie gross die erste Dimension ist..

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johny_D schrieb:
> 1.) Arrays werden in C/C++ eh immer als Referenz und nicht Kopie
> übergeben. Daher ist eine Übergabe mit Pointer eh unnötig.

Soweit schon richtig. Es geht auch mit Pointern, aber dazu muss man dann 
halt das Layout eines n-dimensionalen Arrays kennen - insbesondere die 
konkreten n-1 Dimensionen, siehe auch 2). Dann kann man den Pointer auf 
ein bestimmtes Element von [x][y] auch ausrechnen. Aber warum selbst die 
Arbeit machen, soll der Compiler halt schuften. Der Arrayzugriff ist 
deshalb auch nicht ineffektiver. Intern macht der Compiler (wenn er gut 
ist und mehrere sequentielle Zugriffe auf nacheinanderfolgende Elemente 
sieht) sogar selber Pointerzugriffe daraus.

> 2.) Es muss nur die 2te Dimension vom Array spezifiziert werden, da die
> Daten wie folgt im speicher liegen

Korrekt. Allgemein ausgedrückt: Du musst dem Compiler n-1 Dimensionen 
bekanntgeben.

> daher spielt es keine Rolle wie gross die erste Dimension ist..

Ja.

: Bearbeitet durch Moderator
von Karl H. (kbuchegg)


Lesenswert?

Michael .. schrieb:
>
1
void Test_Function(char **Test_Char, int Text_Lenght)
2
> {
3
>   Test_Char[x][y] = 'B';
4
> }
5
> 
6
> #define  Text_lenght 20
7
> 
8
> char Test_Char[20][Text_lenght];
9
> 
10
> 
11
> int main(void)
12
> {
13
> 
14
>   Test_Function(Test_Char, Text_lenght);
15
> }


Nope.
Falsch.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Karl H. schrieb:
> Nope.
> Falsch.

Wurde oben durch praktische Anwendung schon gezeigt ;-)

von Karl H. (kbuchegg)


Lesenswert?

Johny_D schrieb:

> 1.) Arrays werden in C/C++ eh immer als Referenz und nicht Kopie
> übergeben. Daher ist eine Übergabe mit Pointer eh unnötig.

Exakt.
Der Name eines Arrays fungiert an den meisten Stellen als die Adresse 
zum ersten Element des Arrays. Die Anzahl der Dimensionen ist dabei 
unerheblich.

> 2.) Es muss nur die 2te Dimension vom Array spezifiziert werden, da die
> Daten wie folgt im speicher liegen
> arr[0][0]
> arr[0][1]
> arr[0][2]
> arr[1][0]
> arr[1][1]
>
> daher spielt es keine Rolle wie gross die erste Dimension ist..

exakt. Man sagt auch "der letzte Index läuft am schnellsten"

Du kannst es ja mal selbst ausprobieren:
Wenn alle Arrayalemente eines 2D Arrays hintereinander im Speicher 
liegen
1
  +---+---+---+---+---+---+
2
  |   |   |   |   |   |   |
3
  +---+---+---+---+---+---+

und die die Größe der 2.ten Dimension nicht kennst, wo beginnt dann die 
2-te Zeile in diesem 2D Array
(ok, aus technischen Gründen kann das nur ein 2*3 oder ein 3*2 Array 
sein. Nichts desto trotz: Beginnt die 2.te Zeile hier
1
  +---+---+---+---+---+---+
2
  |   |   |   |   |   |   |
3
  +---+---+---+---+---+---+
4
            ^
5
            |
oder beginnt sie hier
1
  +---+---+---+---+---+---+
2
  |   |   |   |   |   |   |
3
  +---+---+---+---+---+---+
4
                ^
5
                |


Welches ist in diesem 2D Array (von dem wir die Dimensionen nicht 
kennen) das Element mit dem Index [1][1]? Wie rechnest du [1][1] um in 
die Nummer des Elements in der linearen Abfolge?

Wenn ich dir aber sage, dass jede Zeile 3 Elemente lang ist, ist alles 
klar.
)

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Frank M. schrieb:
> Karl H. schrieb:
>> Nope.
>> Falsch.
>
> Wurde oben durch praktische Anwendung schon gezeigt ;-)

Schön.
Die Frage ist nur warum das falsch ist.

Weil es sich bei einem
1
char test[2][3];

nun mal nicht um ein Array von Pointer handelt, wobei jeder Pointer 
wieder auf ein Array von 3 chars zeigt, also so etwas
1
   +----+                      +---+---+---+
2
   | ------------------------->|   |   |   |
3
   +----+                      +---+---+---+
4
   | ----------+
5
   +----+      |      +---+---+---+
6
               +----->|   |   |   |
7
                      +---+---+---+

sondern um eine ganz normale lineare Abfolge von 6 Speicherzellen im 
Speicher, deren Adressierung so gestaltet wird, dass die 2. Zeile am 
3.ten Element im Speicher anfängt
1
    +---+---+---+---+---+---+
2
    |   |   |   |   |   |   |
3
    +---+---+---+---+---+---+

Daher wäre char** völlig falsch.
Auf diese Idee zu kommen, zeigt dass da mal wieder wer (nicht du) auf 
die schlampige Aussage reingefallen ist, wonach Arrays Pointer wären. 
Dem ist nicht so!

von Dirk B. (dirkb2)


Lesenswert?

Johny_D schrieb:
> Gibt es eine Möglichkeit diese Referenz in der Funktion wieder "normal"
> über [x][y] adressieren zu können?
J, wurde schon gezeigt

Johny_D schrieb:
> Gibt es eine Möglichkeit die Text_lenght (also die grösse der ersten
> Dimension des Array) nicht mitgeben zu müssen?
Darum wirst du nicht drumrum kommen.
Entweder in der Deklaration beim Paramter vom Array oder als 
Extraparameter wie bei dir.

Aber du kannst das gecaste etwas eindämmen
1
void Test_Function(char*Test_Char, int Text_Lenght)
2
{
3
   *(Test_Char + (Text_Lenght * 5) + 3) = 'A';
4
   Test_Char[(Text_Lenght * 7) + 8] = 'C';
5
6
}
7
8
9
...
10
  Test_Function(&Test_Char[0][0], Text_lenght);
11
// oder
12
  Test_Function(Test_Char[0], Text_lenght);
13
// oder
14
  Test_Function((char*)Test_Char, Text_lenght);

von Johny_D (Gast)


Lesenswert?

Karl H. schrieb:
> Der Name eines Arrays fungiert an den meisten Stellen als die Adresse
> zum ersten Element des Arrays.

Das ist mir inzwischen auch klar. Die erste Variante die ich gepostet 
hatte, funktionierte ja auch, abgesehen davon dass ich ein durcheinander 
mit Zeilen und spalten gemacht habe und ich das ganze als "viel zu 
unpraktisch um wahr zu sein" empfunden habe.

Karl H. schrieb:
> Daher wäre char** völlig falsch.

Ich frage mich wie völlig falsch den das sein kann, während meiner 
verzweifelten google Runde (mit ganz viel ehren Runden) habe ich diese 
Lösung öfters angetroffen. Mit etwas Aufwand (den aber niemand will) 
könnte man ja auch etwas in die Richtung machen...


Karl H. schrieb:
> die schlampige Aussage reingefallen ist, wonach Arrays Pointer wären.
> Dem ist nicht so!

Bei dem allem was im Internet rumgeistert kann man das aber schnell mal 
glauben.
Denke die Investition in ein vernünftiges C/C++ Buch war keine blöde 
idee. Sowohl zum lernen und als referenz, schon nur um zu wissen was 
jetzt stimmt. Jetzt müsste es nur noch kommen :)

Dirk B. schrieb:
> Aber du kannst das gecaste etwas eindämmen
Jup das würde es definitiv ein wenig übersichtlicher machen. Bleibe aber 
trotzdem bei der Version von Frank, ist eigentlich genau das was ich 
benötige.

Und zum Schluss ein Riesen Danke schön @all für die Hilfe!

von Karl H. (kbuchegg)


Lesenswert?

Johny_D schrieb:

>> Daher wäre char** völlig falsch.
>
> Ich frage mich wie völlig falsch den das sein kann


meine Zeichnung nicht gesehen?

Was ist dir am Unterschied zwischen der Datenstruktur A
1
   +----+                      +---+---+---+
2
   | ------------------------->|   |   |   |
3
   +----+                      +---+---+---+
4
   | ----------+
5
   +----+      |      +---+---+---+
6
               +----->|   |   |   |
7
                      +---+---+---+

und der Datenstruktur B
1
    +---+---+---+---+---+---+
2
    |   |   |   |   |   |   |
3
    +---+---+---+---+---+---+

unklar?

Ist doch klar ersichtlich, dass das im Speicher komplett anders 
aufgebaut ist.


> verzweifelten google Runde (mit ganz viel ehren Runden) habe ich diese
> Lösung öfters angetroffen.

Das es diese Lösung gibt, streitet ja auch keiner ab.

Nur wenn eine Funktion so aussieht
1
void foo( char** check )
2
{
3
  ...
4
}

dann kann ich ihr nicht einen
1
char test[2][3];
vorsetzten. Denn die Funktion erwartet Datenstruktur A, der Aufrufer hat 
aber 'nur' Datenstruktur B.

Im die Funktion zu befriedigen, müsste ich beispielsweise den Aufruf so 
gestalten
1
{
2
  char* data[2];
3
4
  data[0] = malloc(3);
5
  data[1] = malloc(3);
6
}
7
8
oder
9
10
{
11
  char[] txt1 = "Si";
12
  char[] txt2 = "No";
13
14
  char* data[2] = { txt1, txt2 };
15
16
  foo( data );
17
}

dann stimmen Aufrufer und Funktion darüber überein, wie die 
Datenstruktur aussieht.

Das wird sogar sehr oft gemacht, weil es hier recht offensichtlich ist, 
dass die Text eben nicht alle gleich lang sein müssen. Denn den Pointern 
im Array ist es ja egal, wohin sie zeigen und wie lang das ist.
1
{
2
  ...
3
  const char* data[2] = { "Hallo", "du" };
4
  ...
5
}

Aber: Das ist nicht das, was ein
1
  char txt[2][3];

darstellt. Dessen Speicherlayout sieht ganz anders aus.

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

Johny_D schrieb:
> Karl H. schrieb:
>> Daher wäre char** völlig falsch.
>
> Ich frage mich wie völlig falsch den das sein kann, während meiner
> verzweifelten google Runde (mit ganz viel ehren Runden) habe ich diese
> Lösung öfters angetroffen. Mit etwas Aufwand (den aber niemand will)
> könnte man ja auch etwas in die Richtung machen...

Ein Array von Pointern kannst du auch über zwei Klammerpaare 
dereferenzieren.
Das wäre dann ein Doppelpointer.

Ein Beispiel wäre argv von main
1
char *argv[];

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.