2016-03-11 6 views
3

liés, mais un peu différent de Do any compilers transfer effective type through memcpy/memmovetype agnostique memcpy dans C99

En C89, memcpy et memmove sont tenus de se comporter comme si la source et la destination sont accessibles en utilisant des types de caractères, la copie de tous les bits du source à la destination sans tenir compte du type de données en cours de copie. C99 modifie la sémantique de sorte que si un objet avec un type effectif est copié dans un stockage qui n'a pas de type déclaré [généralement un stockage reçu de malloc ou une autre fonction similaire], il crée un objet dans le stockage de destination accessible uniquement en utilisant le type de source. Le code suivant, par exemple, aurait un comportement entièrement défini en C89 sur toutes les plates-formes où "unsigned int" et "unsigned long" ont la même représentation 32 bits, mais auraient un comportement indéfini sous C99.

#include <stdio.h> 
#include <string.h> 
void increment_32_bit_uint(void *p) 
{ 
    uint32_t temp; 
    memcpy(&temp, p, sizeof temp); 
    temp++; 
    memcpy(p, &temp, sizeof temp); 
} 
int main(void) 
{ 
    unsigned *ip = malloc(sizeof (unsigned)); 
    unsigned long *lp = malloc(sizeof (unsigned long)); 
    *ip = 3; *lp = 6; 
    increment_32_bit_uint(ip); 
    increment_32_bit_uint(lp);  
    printf("%u %lu", *ip, *lp); 
    return 0; 
} 

En vertu des règles C99, en passant le stockage alloué à la fonction « increment_32_bit_uint », il sera fixé le type efficace pour uint32_t, qui ne peut pas être le même type que les deux « non signé » et « unsigned long », même si les trois les types ont la même représentation. Par conséquent, un compilateur peut faire tout ce qu'il veut avec du code qui lit ce stockage comme un type autre que uint32_t, même si ce type a la même représentation. Y a-t-il un moyen, en C99 ou C11, d'effectuer la copie de manière à permettre à un compilateur de générer du code efficace, mais de forcer le compilateur à traiter la destination comme s'il contenait un motif de bits sans type effectif [qui pourrait donc être accessible en utilisant n'importe quel type]?

+0

gcc compile votre exemple sans avertissement en utilisant 'std = c99' ou' std = c11' après y compris '' et '' xvan

+2

@xvan: Qu'un compilateur particulier (ou même chaque compilateur qui existe aujourd'hui) arrive à faire quelque chose ne signifie pas que la norme impose une exigence de continuer à le faire.Il y a un certain nombre de cas où presque tous les compilateurs ont eu un comportement identique pendant des décennies sans que la norme ne le leur impose, jusqu'à ce que certains auteurs de compilateurs décident qu'ils n'ont plus besoin de prendre en charge ces cas. compilateurs ne signifie pas qu'il n'invoque pas UB. – supercat

+1

@xvan: Per N1570: "Si une valeur est copiée dans un objet sans type déclaré en utilisant memcpy ou memmove, ou est copiée comme un tableau de type caractère, alors le type effectif de l'objet modifié pour cet accès et pour les accès suivants qui ne modifie pas la valeur est le type effectif de l'objet à partir duquel la valeur est copiée, s'il en a un. " Je ne vois aucune raison de dire que le type effectif ne serait pas défini sur 'uint32_t', ni que la lecture comme tout autre type n'invoquerait pas de comportement indéfini. – supercat

Répondre

0

Vous pouvez vous débarrasser de tous les problèmes de type efficace si vous utilisez simplement un type de retour pour la fonction.

uint32_t increment_32_bit_uint (const void* p) 
{ 
    u32_t result; 
    memcpy(&result, p, sizeof(result)); 
    result++; 
    return result; 
} 

Cela forcera l'appelant à faire attention à leurs types. Bien sûr, en théorie, il s'agit d'une sorte d'objet immuable plutôt que d'un changement sur place de la variable. En pratique cependant, je pense que vous obtiendrez le code le plus efficace de toute façon, si vous l'utilisez comme

x = increment_32_bit_uint(&x); 

En général, je ne vois pas comment les optimisations strictes seraient jamais aliasing utile dans applications réelles si elles ne traitent pas les types stdint.h comme des types compatibles avec leurs équivalents de type de données primitifs. En particulier, il doit traiter uint8_t comme un type de caractère, ou tout code C professionnel de bas niveau se casserait.

La même chose pour votre cas ici. Si le compilateur sait que unsigned int est 32 bit, pourquoi aurait-il décider de provoquer des problèmes d'aliasing pour les utilisateurs de uint32_t et vice versa? Voilà comment vous tournez un compilateur inutile.

+0

Bien que j'utilise un seul élément de données dans cet exemple, des problèmes plus importants se produisent avec les tableaux [par ex. écrire une fonction pour décrémenter chaque élément dans un tableau qui n'est pas déjà zéro] donc l'utilisation de la valeur de retour de la fonction ne fonctionnerait pas. En outre, si vous jouez avec les compilateurs en ligne par exemple. gcc.godbolt.org, vous remarquerez que même si "int" et "long" sont 32 bits, si une fonction accepte à la fois un 'int *' et un 'long *' le compilateur supposera que les deux pointeurs ne peuvent pas accéder le même objet. Il n'est pas rare pour un programme d'avoir des bibliothèques qui attendent un tableau de 'unsigned', d'autres qui attendent ... – supercat

+0

... un tableau de' unsigned long', et d'autres qui attendent un tableau de 'uint32_t', et pour le programme à besoin d'échanger des données entre ces bibliothèques. Il existe également de nombreux cas où les programmes peuvent avoir besoin de travailler avec des pointeurs vers différents types de structures partageant une séquence initiale commune; Les compilateurs d'aujourd'hui ne le permettent cependant pas, sauf si 'memcpy' est utilisé, et même en utilisant' memcpy' les règles de type effectives ne semblent pas offrir une garantie de fonctionnement. – supercat

+0

@supercat Ensuite, je suppose que la seule option est d'utiliser une union, avec un type de punition de/vers un type de char. – Lundin