web-dev-qa-db-fra.com

Sécurité des arguments de fonction C ++

Dans une fonction qui prend plusieurs arguments du même type, comment garantir que l'appelant ne gâche pas la commande?

Par exemple

void allocate_things(int num_buffers, int pages_per_buffer, int default_value ...

et ensuite

// uhmm.. lets see which was which uhh..
allocate_things(40,22,80,...
58
Anonymous Entity

Une solution typique consiste à mettre les paramètres dans une structure, avec des champs nommés.

AllocateParams p;
p.num_buffers = 1;
p.pages_per_buffer = 10;
p.default_value = 93;
allocate_things(p);

Vous n'êtes pas obligé d'utiliser des champs, bien sûr. Vous pouvez utiliser les fonctions membres ou ce que vous voulez.

69
Dietrich Epp

Si vous avez un compilateur C++ 11, vous pouvez utiliser littéraux définis par l'utilisateur en combinaison avec des types définis par l'utilisateur. Voici une approche naïve:

struct num_buffers_t {
    constexpr num_buffers_t(int n) : n(n) {}  // constexpr constructor requires C++14
    int n;
};

struct pages_per_buffer_t {
    constexpr pages_per_buffer_t(int n) : n(n) {}
    int n;
};

constexpr num_buffers_t operator"" _buffers(unsigned long long int n) {
    return num_buffers_t(n);
}

constexpr pages_per_buffer_t operator"" _pages_per_buffer(unsigned long long int n) {
    return pages_per_buffer_t(n);
}

void allocate_things(num_buffers_t num_buffers, pages_per_buffer_t pages_per_buffer) {
    // do stuff...
}

template <typename S, typename T>
void allocate_things(S, T) = delete; // forbid calling with other types, eg. integer literals

int main() {
    // now we see which is which ...
    allocate_things(40_buffers, 22_pages_per_buffer);

    // the following does not compile (see the 'deleted' function):
    // allocate_things(40, 22);
    // allocate_things(40, 22_pages_per_buffer);
    // allocate_things(22_pages_per_buffer, 40_buffers);
}
30
sergej

Jusqu'à présent, deux bonnes réponses, une de plus: une autre approche consisterait à essayer de tirer parti du système de typage dans la mesure du possible, et à créer des caractères typographiques solides. Par exemple, en utilisant boost strong typedef ( http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/strong_typedef.html ).

BOOST_STRONG_TYPEDEF(int , num_buffers);
BOOST_STRONG_TYPEDEF(int , num_pages);

void func(num_buffers b, num_pages p);

Appeler func avec des arguments dans le mauvais ordre serait désormais une erreur de compilation.

Quelques notes à ce sujet. Premièrement, le typedef fort de boost est plutôt daté dans son approche; vous pouvez faire des choses bien plus agréables avec variadic CRTP et éviter complètement les macros. Deuxièmement, cela introduit évidemment des frais généraux, car vous devez souvent effectuer une conversion explicite. Donc, généralement, vous ne voulez pas en abuser. C'est vraiment bien pour les choses qui reviennent encore et encore dans votre bibliothèque. Pas si bon pour les choses qui se présentent comme une seule fois. Ainsi, par exemple, si vous écrivez une bibliothèque GPS, vous devriez avoir un fort double typedef pour les distances en mètres, un fort int64 typedef pour le temps passé Epoch en nanosecondes, et ainsi de suite.

30
Nir Friedman

(Remarque: le message était initialement étiqueté "C")

C99 partir de permet une extension à @ Dietrich Epp idée: composé littéral

struct things {
  int num_buffers;
  int pages_per_buffer;
  int default_value 
};
allocate_things(struct things);

// Use a compound literal
allocate_things((struct things){.default_value=80, .num_buffers=40, .pages_per_buffer=22});

Pourrait même transmettre l'adresse de la structure.

allocate_things(struct things *);

// Use a compound literal
allocate_things(&((struct things){.default_value=80,.num_buffers=40,.pages_per_buffer=22}));
9
chux

Juste pour être complet, vous pouvez utiliser arguments nommés, lorsque votre appel devient.

void allocate_things(num_buffers=20, pages_per_buffer=40, default_value=20);
// or equivalently
void allocate_things(pages_per_buffer=40, default_value=20, num_buffers=20);

Cependant, avec le C++ actuel, cela nécessite un peu de code à implémenter (dans le fichier d'en-tête déclarant allocate_things(), qui doit également déclarer les objets externes appropriés num_buffers Etc. fournissant operator= qui renvoient un objet approprié unique).

---------- exemple de travail (pour sergej)

#include <iostream>

struct a_t { int x=0; a_t(int i): x(i){} };
struct b_t { int x=0; b_t(int i): x(i){} };
struct c_t { int x=0; c_t(int i): x(i){} };

// implement using all possible permutations of the arguments.
// for many more argumentes better use a varidadic template.
void func(a_t a, b_t b, c_t c)
{ std::cout<<"a="<<a.x<<" b="<<b.x<<" c="<<c.x<<std::endl; }
inline void func(b_t b, c_t c, a_t a) { func(a,b,c); }
inline void func(c_t c, a_t a, b_t b) { func(a,b,c); }
inline void func(a_t a, c_t c, b_t b) { func(a,b,c); }
inline void func(c_t c, b_t b, a_t a) { func(a,b,c); }
inline void func(b_t b, a_t a, c_t c) { func(a,b,c); }

struct make_a { a_t operator=(int i) { return {i}; } } a;
struct make_b { b_t operator=(int i) { return {i}; } } b;
struct make_c { c_t operator=(int i) { return {i}; } } c;

int main()
{
  func(b=2, c=10, a=42);
}
7
Walter

Tu ne peux pas. C'est pourquoi il est recommandé d'avoir le moins d'arguments de fonction possible.

Dans votre exemple, vous pouvez avoir des fonctions distinctes comme set_num_buffers(int num_buffers), set_pages_per_buffer(int pages_per_buffer) etc.

Vous vous êtes probablement aperçu que allocate_things n'est pas un bon nom car il n'exprime pas ce que fait réellement la fonction. Surtout, je ne m'attendrais pas à ce qu'il définisse une valeur par défaut.

7
Frank Puffer

Allez-vous vraiment essayer de contrôler toutes les combinaisons d'entiers arbitraires? Et jeter tous les contrôles pour les valeurs négatives/nulles, etc.?

Il suffit de créer deux types d'énumération pour un nombre minimal, moyen et maximal de tampons, et de petites tailles de tampon moyennes et grandes. Ensuite, laissez le compilateur faire le travail et laissez vos employés d'AQ prendre un après-midi:

allocate_things(MINIMUM_BUFFER_CONFIGURATION, LARGE_BUFFER_SIZE, 42);

Ensuite, vous n'avez qu'à tester un nombre limité de combinaisons et vous aurez une couverture à 100%. Les personnes travaillant sur votre code dans 5 ans n'auront besoin que de savoir ce qu'elles veulent réaliser et de ne pas avoir à deviner les nombres dont elles pourraient avoir besoin ou quelles valeurs ont réellement été testées sur le terrain.

Cela rend le code légèrement plus difficile à étendre, mais il semble que les paramètres soient destinés à un réglage des performances de bas niveau, donc les valeurs ne devraient pas être perçues comme bon marché/triviales/ne nécessitant pas de tests approfondis. Un examen du code d'un changement de allocate_something (25, 25, 25);

...à

allocate_something (30, 80, 42);

... obtiendra probablement juste un haussement d'épaules/soufflé, mais un examen du code d'une nouvelle valeur d'énumération EXTRA_LARGE_BUFFERS déclenchera probablement toutes les bonnes discussions sur l'utilisation de la mémoire, la documentation, les tests de performances, etc.

6
Dan Haynes