web-dev-qa-db-fra.com

Passer un tableau à une fonction (et pourquoi cela ne fonctionne pas en C ++)

Je suis tombé sur du code C qui compile, mais je ne comprends pas pourquoi. Plus précisément, j'ai une bibliothèque C qui contient beaucoup de code utilisant ce format:

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 );
    ...
}

Ce que je ne comprends pas, c'est pourquoi le compilateur permet de dimensionner les tableaux. Autant que je sache, les tailles doivent être fixées (par exemple, xu_col[9][7]) ou indéfini (par exemple, xu_col[][]). Dans le code ci-dessus, il apparaît que les tailles ne sont pas des constantes à la compilation.

Le compilateur ignore-t-il simplement les arguments ici? ou fait-il vraiment une vérification au moment de la compilation sur les dimensions?

Si c’est le dernier cas, il semble alors probable que les dimensions soient transmises séparément.

La deuxième partie de la question est la suivante:

Pourquoi la même version ne fonctionne-t-elle pas en C++? Quand je change littéralement l'extension de fichier de .c à .cpp et essayer de recompiler, je reçois

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]);

Je voudrais savoir quel idiome je devrais utiliser pour convertir ce code en C++, car apparemment l'idiome précédent était quelque chose qui fonctionne en C, mais pas en C++.

44
bremen_matt

En C, il est possible d’utiliser des paramètres de fonction pour définir la taille d’un paramètre de tableau de longueur variable tant que la taille précède le tableau dans la liste des paramètres. Ceci n'est pas supporté en C++.

59
Stephen Docy

La raison pour laquelle cela fonctionne en C, mais pas en C++, c'est simplement parce qu'il s'agit de code C et non de C++. Les deux langues partagent une histoire, pas une grammaire.

La méthode C++ pour passer des tableaux de taille variable est std::vector, probablement par référence si vous avez l'intention de modifier le vecteur dans la fonction, ou par constréférence si vous ne le faites pas.

36
MSalters

Ce que je ne comprends pas, c'est pourquoi le compilateur permet de dimensionner les tableaux. Autant que je sache, les tailles doivent être fixes (par exemple, xu_col [9] [7]) ou non définies (par exemple, xu_col [] []). Dans le code ci-dessus, il apparaît que les tailles ne sont pas des constantes à la compilation.

Vous avez raison, les tailles ne sont pas des constantes à la compilation. Si vous avez un tableau à deux dimensions, le compilateur x [line] [col] a besoin du nombre d'éléments d'une ligne pour calculer l'adresse d'un élément. Examinez les exemples de code get_char_2 () et get_char_3 ().

Si vous utilisez des tableaux de longueur variable (VLA) comme paramètres de fonction, vous devez fournir ce nombre (voir l'exemple get_char_1). tu peux écrire:

 my_func( x[][width] )

ou vous pouvez écrire

 my_func( x[999][width] )

Le compilateur ignore-t-il simplement les arguments ici? ou fait-il vraiment une vérification au moment de la compilation sur les dimensions?

Le premier numéro (999) sera ignoré par le compilateur. La seconde est nécessaire. Sans la taille de la ligne, le compilateur ne peut pas calculer les adresses à l'intérieur de ce tableau 2D. Le compilateur ne fait pas de contrôles d'exécution ou de compilation pour les VLA en C.

/* 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;
}
11
Jens Harms

Tout ce que cela fait (en C) vous permet d'écrire du code d'indexation dans la fonction appelée sans avoir à faire le calcul d'adresse vous-même, par exemple:

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

versus

double d= xu_col[i][j];
5
Paul Ogilvie

Lorsqu'un paramètre est déclaré comme ayant un type de tableau à une dimension, C ignore la taille donnée et traite plutôt le paramètre comme un pointeur sur le type d'élément. Pour les matrices imbriquées (multidimensionnelles), un tel traitement est uniquement appliqué à la matrice externe. Dans C89, les dimensions intérieures devaient avoir des tailles fixes, mais dans C99, les dimensions peuvent être des expressions. Si les paramètres nécessaires pour calculer la taille d’un tableau ne sont répertoriés qu'après l’affichage du tableau, il sera nécessaire d’utiliser un curieux mélange d’anciennes et de nouvelles syntaxes pour déclarer la fonction, par exemple.

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;
}

Notez que les tailles de tableau sont spécifiées comme * dans le prototype de la fonction et que la définition de la fonction ne spécifie pas les types dans la liste des arguments, mais décrit plutôt tous les types de paramètres entre la liste des arguments et l'accolade ouvrante. Notez également que même si le compilateur ignore probablement le nombre de lignes dans la déclaration de tableau, un compilateur intelligent peut toutefois l’utiliser pour faciliter l’optimisation. Effectivement, la syntaxe "statique" étrange invite le compilateur à lire toutes les parties du tableau, jusqu'à la taille donnée, comme il l'entend, que les valeurs soient lues ou non par le code. Cela peut être utile sur certaines plates-formes où le code pourrait bénéficier du traitement simultané de plusieurs éléments du tableau.

5
supercat

La difficulté de votre exemple de code est qu’un des paramètres de la fonction est protégé, double xu_col[n_col][n_x + n_u], Où n_x Et n_u Sont des variables et non des constantes. Si vous transmettez simplement ceci en tant que double[] À la place, certains compilateurs C++ pourraient permettre à un transtypage tel que double (&table)[n_col][n_x + n_u] = (double(&)[n_col][n_x + n_u])xu_col; de fonctionner comme une extension non standard, mais l'approche portable consisterait à écrire des accès tels que xu_col[i*(n_x+n_u) + j], que vous pourriez simplifier avec une fonction d'assistance si cela est trop moche.

Une approche alternative, probablement plus conforme à l'esprit de la STL, pourrait consister à écrire une classe de conteneur minimale connaissant ses dimensions, stockant les éléments dans un tableau linéaire pour plus d'efficacité. Ensuite, vous pouvez déclarer redim_array<double> table = redim_array<double>(xu_col, n_col*(n_x+n_u)).redim(n_col, n_x+n_u); et accéder à table(i,j).

Plusieurs autres réponses ont décrit la syntaxe des tableaux de longueur variable, mais un autre aspect de votre question concerne la manière dont il est légal de convertir implicitement un tableau à deux dimensions rectangulaire¹ en un tableau à une dimension.

Ce qui se passe, c'est que le tableau rectangulaire est mis en mémoire en tant qu'éléments consécutifs, de sorte qu'il peut dégénérer en un pointeur sur les éléments, puis le paramètre de fonction peut l'interpréter comme un tableau de géométrie différente.

Voici un petit programme qui montre ce comportement.

#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;
}

Dans les commentaires ci-dessous, il y a un certain langage dans les commentaires ² sur la question de savoir si le fait de passer les arguments du tableau sans les transtypages explicites est techniquement légal par défaut. J'ai choisi de reléguer cela dans une note de bas de page et de simplement supprimer l'exemple sans distribution. Dans le monde réel, vous verrez parfois du code sans la conversion pointeur vers tableau de géométries différentes, ce qui peut générer un avertissement. La disposition de la mémoire des deux tableaux est, selon la norme, identique.

Pour convertir en C++, vous pouvez utiliser l'astuce de conversion de pointeur ou vous pouvez maintenant le coder un peu en utilisant des références.

Voici une traduction C++ du programme ci-dessus. Il faut que constexpr, sauf la première dimension du tableau transmis, soit utilisé, mais certains compilateurs prennent en charge les tableaux de longueur variable de style C99 en tant qu'extension.

#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;
}

¹ Les programmeurs C appellent parfois des tableaux comme int matrix[ROWS][COLS] Ou des tableaux comme char** argv "Tableaux à deux dimensions". Ici, j’appelle le premier rectangulaire et le dernier en lambeaux.

² La contrainte sur les arguments de fonction dans la norme C11 est 'Chaque argument doit avoir un type tel que sa valeur puisse être affectée à un objet avec la version non qualifiée du type de son paramètre correspondant.' En outre 'Une déclaration d'un paramètre comme' 'tableau de type' 'doit être réglé sur' 'pointeur qualifié vers le type' '' et, si cela s'applique de manière récursive, un tableau multidimensionnel d'un type quelconque sera ajusté sur un pointeur plat de ce type.

3
Davislor

en ce qui concerne la deuxième partie de votre question:

Pourquoi la même version ne fonctionne-t-elle pas en C++? Lorsque je change littéralement l'extension de fichier de .c en .cpp et que je tente de la recompiler, je reçois

La source de ce problème est que C++ modifie les noms.

Pour éviter les erreurs de nom lors de l’exécution de C++ et lors de la tentative d’accès à une bibliothèque C.

près du haut du fichier d'en-tête de la bibliothèque C, après la protection d'inclusion multiple, insérez:

#ifdef __cplusplus
extern "C" {
#endif

et vers la fin du fichier d'en-tête, avant le #endif de la protection d'inclusion multiple, insérez:

#ifdef __cplusplus
}
#endif

Cela éliminera le problème des fonctions du fichier de bibliothèque associé non trouvé

0
user3629249