2016-10-10 1 views
6

Je voudrais écrire une fonction générique pour détecter si un tableau de pointeurs vers un type quelconque contient un NULL. Ma première tentative a été quelque chose le long de ces lignes:Ecriture d'une fonction générique pour détecter si un tableau de pointeurs contient NULL

bool find_null (void *ptrs, size_t num_ptrs) { 
    void **array = ptrs; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (array[i] == NULL) return true; 
    } 
    return false; 
} 

Il a été souligné que cela pourrait provoquer une violation stricte aliasing, puisque le tableau de pointeurs vers Foo serait accessible en tant que tableau de pointeurs vers void, qui ne figure pas comme l'un des moyens a permis un objet est autorisé à accéder à C.2011 § 6,5 ¶ 7.

je pouvais réécrire la fonction pour accéder au tableau de pointeurs comme unsigned char * à la place, mais je ne suis pas sûr de savoir comment effectuer la NULL vérifier sans briser stricte aliasing. Quelqu'un peut-il fournir une technique valide?

bool find_null (void *ptrs, size_t num_ptrs) { 
    unsigned char *array = ptrs; 
    void *p; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     memcpy(&p, array + i * sizeof(p), sizeof(p)); 
     if (p == NULL) return true; 
     /* 
     * Above seems to still break strict aliasing. 
     * What should be done instead? 
     */ 
    } 
    return false; 
} 

Le but est d'écrire une fonction générique qui fonctionnerait même en fonction de type spécifique. En d'autres termes, une version générique de la fonction ci-dessous:

bool find_null_Foo (Foo *array[], size_t num_ptrs) { 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (array[i] == NULL) return true; 
    } 
    return false; 
} 
+8

Ce n'est pas possible en général. Il n'est pas nécessaire que 'sizeof (void *)' soit égal à 'sizeof (Foo *)'.Ils peuvent être inégaux sur des machines adressables sans octets, où 'sizeof (void *)' est un peu plus grand que 'sizeof (Foo *)' en raison de la nécessité d'enregistrer l'octet dans le mot référencé par le pointeur. –

+1

Voulez-vous que le code considère uniquement un pointeur NULL avec la valeur '(void *) NULL' ou veut gérer la possibilité que la plate-forme ait plusieurs valeurs de pointeurs NULL? (Bien sûr, tous les pointeurs NULL sont équivalents les uns aux autres - bien qu'ils puissent avoir des codages différents.) – chux

+0

@RaymondChen: Merci d'avoir soulevé cette question, c'est un point valide. J'ai posté une réponse qui devrait en tenir compte. Cependant, pour une telle machine, je finirais probablement par la concevoir comme adressable à 30 bits (pour des mots de 4 octets) ou adressable à 61 bits (pour des mots de 8 octets) au lieu d'essayer de traiter des pointeurs de différentes tailles. – jxh

Répondre

1

Vous ne pouvez pas le faire avec l'interface spécifique que vous présentez, mais vous pouvez être en mesure de le faire de cette façon un peu maladroite:

bool find_null (const void *array, size_t num_ptrs, size_t ptr_size, 
     const void *null) { 
    const char (*ptr_array)[ptr_size] = array; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (!memcmp(array[i], null, ptr_size)) return true; 
    } 
    return false; 
} 

Vous appelleriez que comme ceci:

struct Foo; 

#define ARRAY_SIZE 53 
int main(void) { 
    struct Foo *my_array[ARRAY_SIZE] = { ... }; 
    struct Foo * const foo_null = (struct Foo *) 0; 
    if (find_null(my_array, ARRAY_SIZE, sizeof(*my_array), &foo_null)) { 
     puts("It contains NULL"); 
    } else { 
     puts("It does not contain NULL"); 
    } 
} 

Prenez note que cela suppose qu'il n'y a qu'une seule représentation pour les pointeurs nuls du type en question, ce qui est vrai dans de nombreuses implémentations, mais est pas requis par la langue. Notez également que ce n'est pas vraiment spécifique à la recherche de pointeurs NULL, donc en fait vous pouvez l'utiliser pour rechercher votre tableau de pointeur pour n'importe quelle valeur de pointeur. En fait, il n'est même pas spécifique aux tableaux de pointeurs - vous pouvez l'utiliser pour rechercher n'importe quel tableau pour n'importe quelle valeur, tant que l'égalité byte-for-byte est un critère de correspondance approprié (ce qui n'est pas le cas syndicats, et pourrait ne pas être pour d'autres types). En outre, si cela vous convient, vous pouvez probablement concevoir une macro de wrapper qui facilite l'utilisation de certains de vos scénarios les plus courants.

+0

Il n'y a aucune garantie que vous puissiez comparer des pointeurs NULL de tailles différentes. En fait, il n'y a aucune garantie que 'sizeof (void *)> = taille de (int *)' – chqrlie

+0

@chqrlie est correct. C'est pourquoi la fonction que j'ai présentée accepte un * pointeur vers * la valeur du pointeur nul à comparer avec les éléments du tableau, et utilise 'memcmp()' pour effectuer les comparaisons. Je compare des pointeurs du même type. –

+0

Oups, vous le faites en effet, mais comme vous le mentionnez, il ne semble pas y avoir de garantie qu'il existe une représentation unique des pointeurs NULL pour un type donné. – chqrlie

1

Basé sur Raymond's comment, il semble qu'une fonction générique nécessiterait des arguments supplémentaires. Comme il n'y a aucun moyen de convertir la représentation unsigned char des pointeurs en void *, cela doit être fait via un rappel.

bool find_null_generic (const void *ptrs, size_t ptr_sz, size_t num_ptrs, 
         const void *(*convert)(const void *)) { 
    const unsigned char *array = ptrs; 
    size_t i; 
    for (i = 0; i < num_ptrs; ++i) { 
     if (convert(array + i * ptr_sz) == NULL) return true; 
    } 
    return false; 
} 

Pour le tableau hypothétique de pointeur vers Foo:

const void *convert_Foo (const void *data) { 
    const Foo *foo; 
    memcpy(&foo, data, sizeof(foo)); 
    return foo; 
} 

Foo *foo_array[N] = {...}; 
bool result = find_null_generic(foo_array, sizeof(Foo *), N, convert_Foo); 
1

La fonction générique n'est pas garanti pour fonctionner comme prévu sur les systèmes où des pointeurs vers des types différents peuvent avoir différentes représentations et/ou tailles. Heureusement, de telles architectures sont assez rares de nos jours. Les systèmes compatibles Posix par exemple sont garantis d'utiliser la même représentation et la même taille pour tous les types de pointeurs.

+1

Malheureusement, la violation de l'alias strict peut toujours conduire le compilateur à générer des résultats d'optimisation inattendus, même sur des systèmes où les tailles des pointeurs sont les mêmes. – jxh

+0

@jxh: ce type de comportement est si contre-intuitif qu'il devrait être considéré comme un bug. Je me demande comment l'aliasing strict pourrait être formulé de manière à empêcher des conséquences aussi importantes. – chqrlie

+0

@chqrlie: La formulation la plus simple serait de spécifier que lancer un pointeur de type 'T *' à 'U *' accordera une licence pour utiliser cette cible ou des pointeurs dérivés comme 'U * 'jusqu'à la prochaine fois que l'objet est accessible via n'importe quel pointeur non dérivé du résultat de la distribution susmentionnée. Cela n'exclurait pas beaucoup d'optimisations utiles, mais éviterait probablement le besoin d'utiliser '-fno-strict-aliasing' avec 90% des programmes qui en ont actuellement besoin. – supercat