web-dev-qa-db-fra.com

Implémenter une macro d'échange générique en C

Dupliquer possible:
existe-t-il un équivalent de std :: swap () dans c

Salut les gens,

Je tentais un problème pour écrire une macro d'échange générique en C et ma macro ressemble à ceci:

#define swap(x,y) { x = x + y; y = x - y; x = x - y; }

Cela fonctionne très bien pour les nombres entiers et les flottants, mais je ne suis pas sûr qu’il y ait des problèmes. Que se passe-t-il si par macro générique, on entend échanger des pointeurs, des caractères, etc.? Quelqu'un peut-il m'aider à écrire une macro générique pour permuter toutes les entrées?

Merci

26
Mohit Dhuper

Cela fonctionne bien uniquement avec des entiers.

Pour les flotteurs, il échouera (par exemple, essayez de le lancer avec un très grand et un très petit flottant).

Je suggérerais quelque chose comme suit:

#define swap(x,y) do \ 
   { unsigned char swap_temp[sizeof(x) == sizeof(y) ? (signed)sizeof(x) : -1]; \
     memcpy(swap_temp,&y,sizeof(x)); \
     memcpy(&y,&x,       sizeof(x)); \
     memcpy(&x,swap_temp,sizeof(x)); \
    } while(0)

memcpy est plutôt optimisé lorsque le montant à copier est connu au moment de la compilation ..__ De plus, il n’est pas nécessaire de passer manuellement un nom de type ou d’utiliser des extensions spécifiques au compilateur.

74
adamk

Vous pouvez faire quelque chose comme ça:

#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)

que vous invoqueriez alors comme ceci:

SWAP(a, b, int);

ou:

SWAP(x, y, float);

Si vous préférez utiliser des extensions spécifiques à gcc, vous pouvez améliorer ceci de la manière suivante:

#define SWAP(x, y) do { typeof(x) SWAP = x; x = y; y = SWAP; } while (0)

et alors ce serait juste:

SWAP(a, b);

ou:

SWAP(x, y);

Cela fonctionne pour la plupart des types, y compris les pointeurs.

Voici un programme de test:

#include <stdio.h>

#define SWAP(x, y) do { typeof(x) SWAP = x; x = y; y = SWAP; } while (0)

int main(void)
{
    int a = 1, b = 2;
    float x = 1.0f, y = 2.0f;
    int *pa = &a;
    int *pb = &b;

    printf("BEFORE:\n");
    printf("a = %d, b = %d\n", a, b);
    printf("x = %f, y = %f\n", x, y);
    printf("pa = %p, pb = %p\n", pa, pb);

    SWAP(a, b);     // swap ints
    SWAP(x, y);     // swap floats
    SWAP(pa, pb);   // swap pointers

    printf("AFTER:\n");
    printf("a = %d, b = %d\n", a, b);
    printf("x = %f, y = %f\n", x, y);
    printf("pa = %p, pb = %p\n", pa, pb);

    return 0;
}
56
Paul R

GMan a commencé cette tentative pour la coder en combinant une fonction inline et une macro. Cette solution suppose que vous disposiez d’un compilateur C moderne prenant en charge C99, car il utilise un littéral composé :

inline void swap_detail(void* p1, void* p2, void* tmp, size_t pSize)
{
   memcpy(tmp, p1, pSize);
   memcpy(p1, p2, pSize);
   memcpy(p2 , tmp, pSize);
}
#define SWAP(a, b) swap_detail(&(a), &(b), (char[(sizeof(a) == sizeof(b)) ? (ptrdiff_t)sizeof(a) : -1]){0}, sizeof(a))

Cela a les propriétés suivantes:

  • Il évalue chacune des a et b seulement Une fois.
  • Il a une vérification du temps de compilation pour les tailles correctes
  • Il n’ya pas de problème de nommage avec une variable cachée
  • La taille de la variable temporaire est Calculée lors de la compilation, le littéral composé N'est donc pas un tableau dynamique

La transposition (ptrdiff_t) est nécessaire pour que -1 ne soit pas promu silencieusement à SIZE_MAX.

Cette solution présente encore deux inconvénients:

  1. Ce n'est pas type sûr. Il vérifie seulement Pour la taille des types, pas Leur sémantique. Si les types Diffèrent, dites une double de taille 8 et Un uint64_t, vous êtes en difficulté.

  2. Les expressions doivent permettre à l'opérateur & d'être applicable. Ainsi, __.it ne fonctionnera pas avec les variables déclarées avec la classe registerstorage.

11
Jens Gustedt

Autrement dit: vous ne pouvez pas créer une macro d'échange generic en C, du moins, sans risque ni maux de tête. (Voir d'autres articles. Les explications se trouvent ci-dessous.)

Les macros sont bien, mais le problème que vous rencontrez avec le code réel serait des problèmes de type de données (comme vous l'avez dit). De plus, les macros sont "stupides" d'une certaine manière. Par exemple:

Avec votre exemple de macro #define swap(x,y) { x = x + y; y = x - y; x = x - y; }, swap(++x, y) se transforme en { ++x = ++x + y; y = ++x - y; ++x = ++x - y;}.

Si vous avez exécuté int x = 0, y = 0; swap(++x, y);, vous obtiendrez x=2, y=3 au lieu de x=0, y=0. De plus, si l'une des variables temporaires de votre macro apparaît dans votre code, vous risquez de rencontrer des erreurs gênantes.

Les fonctionnalités que vous recherchez ont été introduites dans C++ en tant que modèles. Le plus proche que vous pouvez obtenir en C utilise une fonction en ligne pour chaque type de données imaginable ou une macro relativement complexe (voir les problèmes de macro et les articles précédents).

Voici à quoi ressemble la solution utilisant des modèles en C++:

template<typename T>
inline void swap(T &x, T &y)
{
    T tmp = x;
    x = y; y = tmp;
}

En C, vous avez besoin de quelque chose comme:

inline void swap_int(int *x, int *y) { /* Code */ }
inline void swap_char(char *x, char *y) { /* Code */ }
// etc.

ou (comme mentionné plusieurs fois) une macro assez complexe qui peut être dangereuse.

9
Mr. Llama

Cela ne fonctionne pas forcément bien pour int en fonction du standand. Imaginons le cas où x et y étaient respectivement INT_MAX et INT_MAX-1. La première instruction d'ajout entraînerait un débordement signé qui n'est pas défini par la norme. 

Une implémentation plus fiable de la macro d'échange pour int serait l'algorithme d'échange XOR

0
JaredPar