Array an eine Funktion übergeben (und warum es in C++ nicht funktioniert)

Lesezeit: 13 Minuten

[*]
bremen_matt

Ich bin auf einigen C-Code gestoßen, der kompiliert, aber ich verstehe nicht warum. Insbesondere habe ich eine C-Bibliothek, die viel Code mit diesem Format enthält:

void get_xu_col(int i_start,
                int n,
                double x[n],
                int n_x,
                int n_u,
                int n_col,
                double xu_col[n_col][n_x + n_u]){
    ... 
}

int main(){
    ...
    double xu_col[n_col][n_x + n_u];
    get_xu_col( ..., xu_col );
    ...
}

Was ich nicht verstehe, ist, warum der Compiler die Größenanpassung in den Arrays zulässt. Nach meinem besten Verständnis müssen entweder die Größen festgelegt werden (z xu_col[9][7]) oder undefiniert (zB xu_col[][]). Im obigen Code scheint es, dass die Größen keine Konstanten zur Kompilierzeit sind.

Ignoriert der Compiler hier einfach die Argumente? oder führt es wirklich eine Überprüfung der Dimensionen während der Kompilierung durch?

Wenn letzteres der Fall ist, dann erscheint es fehleranfällig, die Dimensionen separat zu übergeben.

Der zweite Teil der Frage lautet:

Warum funktioniert die gleiche Version nicht in C++? Wenn ich buchstäblich die Dateiendung aus ändere .c zu .cpp und versuchen, neu zu kompilieren, bekomme ich

candidate function not viable: no known conversion from 'double [n_col][n_x + n_u]' to 'double (*)[n_x + n_u]' for 7th argument
void get_xu_col(int i_start, int n, double x[n], int n_x, int n_u, int n_col, double xu_col[n_col][n_x + n_u]);

Ich würde gerne wissen, welches Idiom ich verwenden sollte, um diesen Code in C++ zu konvertieren, da anscheinend das vorherige Idiom etwas war, das in C funktioniert, aber nicht in C++.

  • Interessanterweise sollte der Compiler die Werte der Argumente, die als Array-Größen verwendet werden, tatsächlich ignorieren, es sei denn, Sie zwingen ihn, sie durch Hinzufügen zu berücksichtigen static (z.B double x[static n]). Trotz suchen wie es etwas tut (und nicht in C ++ funktioniert und die Werte gültige/innerhalb des Bereichs liegende Ausdrücke sein müssen), ist das Formular in der Frage etwas nutzlos. Nerviges Loch in der Sprache.

    – Leuschenko

    21. Februar 2018 um 8:54 Uhr


  • Ich verstehe nicht, was Sie das Wort sagen static würde tun … Würde das Schlüsselwort hinzufügen static Machen Sie es zu einer Prüfung zur Kompilierzeit?

    – bremen_matt

    21. Februar 2018 um 19:19 Uhr

  • Relevant: Wie verwende ich Arrays in C++?.

    – sbi

    21. Februar 2018 um 19:32 Uhr

  • Falls Sie es nicht wissen, ist es möglich, C-Code in C++-Projekten zu verwenden, ohne “den Code in C++ zu konvertieren”.

    – MM

    22. Februar 2018 um 5:02 Uhr

  • @MM OP sagte, er habe gerade die Erweiterung der Datei konvertiert und sei beim Kompilieren fehlgeschlagen.

    – ein besorgter Bürger

    22. Februar 2018 um 6:55 Uhr

In C ist es möglich, Funktionsparameter zu verwenden, um die Größe eines Array-Parameters variabler Länge zu definieren, solange die Größe vor dem Array in der Parameterliste steht. Dies wird in C++ nicht unterstützt.

  • Vielleicht möchten Sie hinzufügen, welche Version das hinzugefügt hat.

    – Deduplizierer

    21. Februar 2018 um 14:21 Uhr

  • VLAs wurden in C99 hinzugefügt, beließen aber den Standard (optionale Unterstützung) für C11.

    – TWhelan

    21. Februar 2018 um 14:28 Uhr

Benutzeravatar von MSalters
MSalter

Der Grund, warum es in C funktioniert, aber nicht in C++, liegt einfach daran, dass es sich um C-Code und nicht um C++ handelt. Die beiden Sprachen teilen eine Geschichte, keine Grammatik.

Die C++-Methode zum Übergeben von Arrays mit variabler Größe ist std::vectorwahrscheinlich von Hinweis wenn Sie beabsichtigen, den Vektor in der Funktion zu ändern, oder durch const Hinweis wenn nicht.

  • Ich dachte, Sie müssten verwenden [*] für Array-Funktionsparameter variabler Länge in C.

    – Bathseba

    21. Februar 2018 um 8:51 Uhr

  • @Bathsheba: Das erste Mal, als ich es sah [*] war bereits in einem Kontext, der erklärt, warum [N] habe dort auch gearbeitet. Aber keine der beiden Syntaxen wird in C++ funktionieren – dies ist eine C-Erfindung, nachdem C++ abgespalten wurde.

    – MSalter

    21. Februar 2018 um 8:55 Uhr

  • Abgewertet, weil die einfache Wiederholung der in der Frage angegebenen Tatsache keine Antwort auf das Warum ist. Ich vermute, die Antwort liegt wahrscheinlich darin, dass es in C99 oder später hinzugefügt wurde (lange nachdem C++ als Beinahe-Obermenge von C erstellt wurde), aber ich kenne nicht genug C, um mehr als Spekulation zu sein.

    – Nye

    21. Februar 2018 um 9:57 Uhr


  • @Nye: Es gibt Teile von C99, die es in C++ geschafft haben, also ist das nicht der Grund. Aber C++ hatte das schon std::vector seit 1998. Es brauchte keine Arrays variabler Länge.

    – MSalter

    21. Februar 2018 um 10:06 Uhr

  • @bremen_matt: Wie kommst du darauf, dass die C-Version eine Kompilierzeitprüfung für ein Array mit variabler Länge zur Laufzeit durchführen könnte?

    – DevSolar

    21. Februar 2018 um 12:25 Uhr

Was ich nicht verstehe, ist, warum der Compiler die Größenanpassung in den Arrays zulässt. Nach meinem besten Verständnis müssen entweder die Größen festgelegt werden (z. B. xu_col[9][7]) oder undefiniert (zB xu_col[][]). Im obigen Code scheint es, dass die Größen keine Konstanten zur Kompilierzeit sind.

Sie haben Recht, die Größen sind keine Kompilierzeitkonstanten. Wenn Sie ein zweidimensionales Array haben, x[line][col] Der Compiler benötigt die Anzahl der Elemente in einer Zeile, um die Adresse eines Elements zu berechnen. Sehen Sie sich den Beispielcode get_char_2() und get_char_3() an.

Wenn Sie Arrays mit variabler Länge (VLAs) als Funktionsparameter verwenden, müssen Sie diese Nummern angeben (siehe Beispiel get_char_1). Du kannst schreiben:

 my_func( x[][width] )

oder du kannst schreiben

 my_func( x[999][width] )

Ignoriert der Compiler hier einfach die Argumente? oder macht es wirklich eine > Kompilierungszeitüberprüfung der Dimensionen?

Die erste Zahl (999) wird vom Compiler ignoriert. Der zweite wird benötigt. Ohne die Zeilengröße kann der Compiler keine Adressen in diesen 2D-Arrays berechnen. Der Compiler führt keine Laufzeit- oder Kompilierungsprüfungen für VLAs in C durch.

/* file: vla.c
 *
 * variable length array example
 *
 * compile with:
 *   
 *    gcc -g -Wall -o vla vla.c 
 *
 */

#include <stdio.h>
#include <wchar.h>


/* 4 Lines - each line has 8 wide-characters */
wchar_t tab[][8] = {
{ L"12345678" },
{ L"abcdefgh" },
{ L"ijklmnop" },
{ L"qrstuvwx" }
};

/* memory layout:   
   0x00:   0x0031  0x0032 0x0033  0x0034  0x0035  0x0036  0x0037  0x0038 
   0x20:   0x0061  0x0062 0x0063  0x0064  0x0065  0x0066  0x0067  0x0068 
   ...

*/



/* get character from table w/o variable length array and w/o type */
char get_char_3(int line, int col, int width, int typesize, void *ptr )
{
char ch = * (char *) (ptr + width * typesize * line + col * typesize ); 

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}


/* get character from table w/o variable length array */
char get_char_2(int line, int col, int width, wchar_t *ptr)
{
char ch = (char) (ptr + width * line)[col]; 

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}

/* get character from table : compiler does not know line length for 
   address calculation until you supply it (width). 
*/
char get_char_1(int line, int col, int width, wchar_t aptr[][width] )
{
/* run-time calculation: 
   (width * sizeof(char) * line)  + col 
     ???    KNOWN          KOWN     KNOWN
*/
char ch = (char) aptr[line][col];

printf("line:%d col:%d char:%c\n", line, col, ch ); 
return ch;
}


int main(void)
{
char ch;

ch = tab[1][7]; /* compiler knows line length */
printf("at 1,7 we have: %c\n",  ch );

/* sizeof tab[0][0] == sizeof(wchar_t) */ 

ch = get_char_1(1,7, sizeof(tab[0])/sizeof(tab[0][0]), tab);
printf("1 returned char: %c\n", ch );

ch = get_char_2(1,7, sizeof(tab[0])/sizeof(tab[0][0]), (wchar_t*)tab);
printf("2 returned char: %c\n", ch );

ch = get_char_3(1,7, sizeof(tab[0])/sizeof(tab[0][0]),
        sizeof( wchar_t), tab);
printf("3 returned char: %c\n", ch );

printf("table size: %lu, line size: %lu,  element size: %lu\n",
       sizeof(tab),
       sizeof(tab[0]),
       sizeof(tab[0][0])
       );

printf("number of elements per lines: %lu\n",
       sizeof(tab[0])/sizeof(tab[0][0]));


printf("number of lines: %lu\n",
       sizeof(tab)/sizeof(tab[0]));

return 0;
}

Alles, was es (in C) tut, ist, dass Sie Indizierungscode in die aufgerufene Funktion schreiben können, ohne die Adressberechnung selbst durchführen zu müssen, zum Beispiel:

double d= xu_col[i*row_size + j]; //get element [i,j]

gegen

double d= xu_col[i][j];

Benutzeravatar von Supercat
Superkatze

Wenn ein Parameter als eindimensionaler Array-Typ deklariert wird, ignoriert C die angegebene Größe und behandelt den Parameter stattdessen als Zeiger auf den Elementtyp. Bei verschachtelten (mehrdimensionalen) Arrays wird eine solche Behandlung nur auf das äußere Array angewendet. In C89 mussten Innenmaße feste Größen haben, aber in C99 können die Maße Ausdrücke sein. Wenn Parameter, die zur Berechnung der Größe eines Arrays benötigt werden, erst nach dem Array aufgelistet werden, ist es notwendig, eine merkwürdige Mischung aus alter und neuer Syntax zu verwenden, um die Funktion zu deklarieren, z

int findNonzero(short dat[*][*], int rows, int cols);
int findNonzero(dat, rows, cols)
    int rows,cols;
    short dat[static rows][cols];
{
    for (int i=0; i<rows; i++)
        for (int j=0; j<cols; j++)
            if (dat[i][j] != 0) return i;
    return -1;
}

Beachten Sie, dass die Array-Größen angegeben sind als * im Funktionsprototyp, und dass die Funktionsdefinition keine Typen in der Argumentliste angibt, sondern stattdessen alle Parametertypen zwischen der Argumentliste und der öffnenden geschweiften Klammer beschreibt. Beachten Sie auch, dass der Compiler zwar wahrscheinlich die Anzahl der Zeilen in der Array-Deklaration ignoriert, ein intelligenter Compiler sie jedoch möglicherweise verwenden kann, um die Optimierung zu erleichtern. Tatsächlich lädt die seltsame “statische” Syntax den Compiler dazu ein, beliebige Teile des Arrays bis zur gegebenen Größe zu lesen, wie er es für richtig hält, unabhängig davon, ob die Werte vom Code gelesen werden oder nicht. Dies kann auf einigen Plattformen hilfreich sein, auf denen Code von der gleichzeitigen Verarbeitung mehrerer Elemente des Arrays profitieren könnte.

Die Schwierigkeit bei Ihrem Codebeispiel besteht darin, dass einer der Funktionsparameter ein Prototyp ist. double xu_col[n_col][n_x + n_u]wo n_x und n_u sind Variablen, keine Konstanten. Wenn Sie dies einfach als übergeben double[] Stattdessen erlauben einige C++-Compiler möglicherweise eine Umwandlung wie z double (&table)[n_col][n_x + n_u] = (double(&)[n_col][n_x + n_u])xu_col; als nicht standardmäßige Erweiterung funktionieren, aber der portable Ansatz wäre, Zugriffe wie zu schreiben xu_col[i*(n_x+n_u) + j]die Sie mit einer Hilfsfunktion vereinfachen könnten, wenn das zu hässlich ist.

Ein alternativer Ansatz, der wahrscheinlich eher dem Geist der STL entspricht, könnte darin bestehen, eine minimale Containerklasse zu schreiben, die ihre Abmessungen kennt und Elemente aus Effizienzgründen in einem linearen Array speichert. Dann könntest du erklären redim_array<double> table = redim_array<double>(xu_col, n_col*(n_x+n_u)).redim(n_col, n_x+n_u); und zugreifen table(i,j).

Einige der anderen Antworten haben die Syntax von Arrays variabler Länge beschrieben, aber ein weiterer Aspekt Ihrer Frage ist, wie es legal ist, ein rechteckiges¹ zweidimensionales Array implizit in ein eindimensionales Array zu konvertieren.

Was passiert, ist, dass das rechteckige Array als aufeinanderfolgende Elemente im Speicher angeordnet ist, sodass es zu einem Zeiger auf die Elemente degenerieren kann, und der Funktionsparameter dies dann als Array mit einer anderen Geometrie interpretieren kann.

Hier ist ein kurzes kleines Programm, das dieses Verhalten demonstriert.

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define ROWS 2
#define COLS 4
#define ELEMS (ROWS*COLS)

int flatten_array( const ptrdiff_t n, const int a[n] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < n; ++i )
    printed += printf( "%d ", a[i] );

  return printed + printf("\n");
}

int rectangular_array( const ptrdiff_t m,
                       const ptrdiff_t n,
                       const int a[m][n] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < m; ++i ) {
    for ( ptrdiff_t j = 0; j < n; ++j )
      printed += printf( "%d ", a[i][j] );

    printed += printf("\n");
  }

  return printed + printf("\n");
}

int main(void)
{
  static const int matrix[ROWS][COLS] = {
    {11, 12, 13, 14},
    {21, 22, 23, 24}
  };
  static const int vector[ELEMS] = {11, 12, 13, 14, 21, 22, 23, 24};

  flatten_array( ELEMS, *(const int (*const)[ELEMS])matrix );
  printf("\n");
  rectangular_array( ROWS, COLS, *(const int (*const)[ROWS][COLS])vector );

  return EXIT_SUCCESS;
}

In den Kommentaren unten² gibt es einige sprachliche Auseinandersetzungen darüber, ob das Übergeben der Array-Argumente ohne die expliziten Umwandlungen standardmäßig technisch legal ist. Ich habe mich entschieden, das in eine Fußnote zu verlagern und einfach das Beispiel ohne Besetzungen zu löschen. In der realen Welt sehen Sie manchmal Code ohne die Umsetzung des Zeigers auf ein Array mit unterschiedlicher Geometrie, und es kann eine Warnung generiert werden. Das Speicherlayout der beiden Arrays muss laut Standard gleich sein.

Um nach C++ zu konvertieren, können Sie den Zeigerkonvertierungstrick verwenden, oder Sie können jetzt ein wenig Code-Golf verwenden, indem Sie Referenzen verwenden.

Hier ist eine C++-Übersetzung des obigen Programms. Es erfordert, dass alle bis auf die erste Dimension des Arrays übergeben werden constexpraber einige Compiler unterstützen Arrays mit variabler Länge im C99-Stil als Erweiterung.

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

constexpr ptrdiff_t rows = 2;
constexpr ptrdiff_t cols = 4;
constexpr ptrdiff_t elems = rows * cols;

int flatten_array( const ptrdiff_t n, const int a[] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < n; ++i )
    printed += printf( "%d ", a[i] );

  return printed + printf("\n");
}

int rectangular_array( const ptrdiff_t n, const int a[][cols] )
{
  int printed = 0;

  for ( ptrdiff_t i = 0; i < n; ++i ) {
    for ( ptrdiff_t j = 0; j < cols; ++j )
      printed += printf( "%d ", a[i][j] );

    printed += printf("\n");
  }

  return printed + printf("\n");
}

int main(void)
{
  static const int matrix[rows][cols] = {
    {11, 12, 13, 14},
    {21, 22, 23, 24}
  };
  static const int vector[elems] = {11, 12, 13, 14, 21, 22, 23, 24};

  flatten_array( elems, (const int(&)[elems])matrix );
  printf("\n");
  rectangular_array( rows, (const int(&)[rows][cols])vector );

  return EXIT_SUCCESS;
}

¹ C-Programmierer nennen beide Arrays manchmal wie int matrix[ROWS][COLS] oder Arrays wie char** argv „zweidimensionale Arrays“. Hier nenne ich ersteres rechteckig und letzteres zottig.

² Die Einschränkung für Funktionsargumente im C11-Standard lautet: ‘Jedes Argument muss einen solchen Typ haben, dass sein Wert einem Objekt mit der nicht qualifizierten Version des Typs seines entsprechenden Parameters zugewiesen werden kann.’ Weiterhin soll ‘Eine Deklaration eines Parameters als ‘array of type’ zu ‘qualified pointer to type” und, wenn dies rekursiv gilt, ein multidimensionales Array irgendeines Typs zu einem flachen Zeiger davon angepasst werden Typ.

Benutzeravatar von user3629249
Benutzer3629249

zum zweiten teil deiner frage:

Warum funktioniert die gleiche Version nicht in C++? Wenn ich die Dateierweiterung buchstäblich von .c in .cpp ändere und versuche, neu zu kompilieren, bekomme ich

Die Ursache dieses Problems liegt darin, dass C++ Namen verstümmelt.

Um Namensverstümmelungen zu vermeiden, wenn C++ ausgeführt wird und versucht wird, auf eine C-Bibliothek zuzugreifen.

am Anfang der Header-Datei für die C-Bibliothek, nach der Mehrfacheinschluss-Wächter-Einfügung:

#ifdef __cplusplus
extern "C" {
#endif

und am Ende der Header-Datei, vor der #endif des Mehrfacheinschlussschutzes einfügen:

#ifdef __cplusplus
}
#endif

Dadurch wird das Problem beseitigt, dass Funktionen in der zugehörigen Bibliotheksdatei nicht gefunden werden

  • Für diejenigen, die diese Antwort abgelehnt haben, WARUM haben Sie sie abgelehnt?

    – Benutzer3629249

    23. Februar 2018 um 18:13 Uhr

  • Ich habe nicht abgelehnt, aber es hat mehr mit VLAs zu tun als mit Namensverstümmelung?

    – wbkang

    26. Februar 2018 um 23:30 Uhr


  • Der ursprüngliche Text der Frage (wenn er anscheinend drastisch bearbeitet wurde) fragt, warum eine Funktion in einer C-Bibliothek nicht gefunden werden konnte, wenn die aufrufende Funktion aus einem C++-Programm stammt.

    – Benutzer3629249

    27. Februar 2018 um 6:35 Uhr

  • Nun, das ist sehr dumm. Ich denke, das passiert eigentlich ziemlich häufig.

    – wbkang

    27. Februar 2018 um 22:25 Uhr

  • Die ursprüngliche Frage bezieht sich auf einen Compilerfehler, nicht auf einen Linkerfehler, daher kann ich nicht erkennen, wie Namensverstümmelung für DIESE Frage relevant ist. Und wenn der gesamte Code als C ++ kompiliert wird (was durch die Frage impliziert wird, dass der Dateityp auf .cpp geändert wird), ist das Verstümmeln von Namen immer noch kein Problem.

    – Steve Kidd

    3. März 2018 um 12:05 Uhr


1407560cookie-checkArray an eine Funktion übergeben (und warum es in C++ nicht funktioniert)

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy