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
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 constHinweis 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];
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.
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
14075600cookie-checkArray an eine Funktion übergeben (und warum es in C++ nicht funktioniert)yes
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.Bdouble 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ügenstatic
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