2016-12-22 1 views
0

Potentiellement apparenté 30970251, 7687082. J'envisage d'écrire un allocateur de mémoire et d'essayer de comprendre comment naviguer dans les restrictions que le C moderne impose aux punitions et alias de type. Je pense que je suis en clair tant que le tampon sous-jacent à l'allocateur a été récupéré à partir de malloc car les pointeurs de malloc n'ont pas de type déclaré.L'affectation change-t-elle le type effectif d'une variable?

Un tampon char suraligné a un type déclaré. Je ne peux pas penser Je peux jeter un pointeur dedans à un type arbitraire et doit à la place écrire soigneusement à lui par un pointeur de char, par ex. en utilisant memcpy. C'est douloureux parce que je ne peux pas voir un moyen de cacher l'écriture via le piratage memcpy de l'appelant.

Considérez ce qui suit:

#include <assert.h> 
#include <stdalign.h> 
#include <stdint.h> 
#include <string.h> 

static_assert(sizeof(double) == sizeof(uint64_t), ""); 
static_assert(alignof(double) == alignof(uint64_t), ""); 

int main(void) 
{ 
    alignas(alignof(double)) char buffer[sizeof(double)]; 
    // effective type of buffer is char [8]                                                 

    { 
    double x = 3.14; 
    memcpy(&buffer, &x, sizeof(x)); 
    // effective type of buffer is now double                                                
    } 

    { 
    uint64_t* ptr = (uint64_t*)&buffer; 
    // effective type of buffer is still double                                               
    // reading from *ptr would be undefined behaviour                                              
    uint64_t y = 42; 
    memcpy(ptr, &y, sizeof(y)); 
    // effective type of buffer is now uint64_t                                               
    } 

    { 
    double* ptr = (double*)&buffer; 
    // effective type of buffer is still uint64_t                                               
    uint64_t retrieve = *(uint64_t*)ptr; // OK                                               
    assert(retrieve == 42); 

    double one = 1.0; 
    *ptr = one; // Unsure if OK to dereference pointer of wrong type                                          
    // What is the effective type of buffer now?                                               
    assert(*ptr == one); 
    } 
} 

Ceci fonctionne dans ce que je peux avec diligence faire en sorte que chaque fois qu'un allocateur personnalisé renvoie un pointeur un vide, il est écrit avec memcpy, au lieu de fonte du type souhaité. À savoir, remplacer

double * x = my_malloc(sizeof(double)); 
*x = 3.14; 

avec:

double tmp = 3.14; 
void * y = my_malloc(sizeof(double)); 
memcpy(y, &tmp, sizeof(double)); 
double * x = (double*)y; 

Tout ce bruit de ligne se tué par l'optimisation passe dans le compilateur, mais ne semble stupide. Est-il nécessaire d'être conforme aux normes?

Cela peut certainement être résolu en écrivant l'allocateur dans ASM au lieu de C mais je ne suis pas particulièrement désireux de le faire. S'il vous plaît laissez-moi savoir si la question est sous-spécifiée.

Répondre

1

Non, pas généralement. Il ne change que le type effectif d'objets qui n'ont pas de type quand ils sont alloués, c'est-à-dire qui sont alloués par malloc et amis.

Donc, si vous faites des choses comme l'utilisateur d'une implémentation de compilateur et de bibliothèque le comportement de votre programme est indéfini. Un tableau qui est alloué comme char[] a toujours le type effectif de cela.

Si vous êtes un rédacteur de compilation ou de bibliothèque, vous n'êtes pas lié à ces restrictions. Il suffit de convaincre votre chaîne d'outils de ne pas trop optimiser les choses. Généralement, vous pouvez le faire en vous assurant que votre fonction d'allocateur vit dans une UT qui n'exporte qu'un void*, et assurez-vous que vous n'avez pas d'optimisation du temps de liaison ou d'autres choses comme cela activé.

Si vous fournissez une telle fonction dans le cadre de la bibliothèque C (remplacement), vous êtes alors l'implémenteur qui doit donner les garanties à vos utilisateurs.

+0

Merci. C'est essentiellement ce que je craignais. Potentiellement mauvaises nouvelles pour lier statiquement une libc avec LTO activé. Définitivement une mauvaise nouvelle pour les allocateurs au niveau de l'application. Je suis de plus en plus sûr que simplifier TBAA ne vaut pas le prix de transformer l'intention claire du développeur en UB. –

+0

@JonChesterfield: Je soupçonne fortement que les auteurs de C89 voulaient que les auteurs du compilateur l'interprètent comme disant que les compilateurs ne doivent pas supposer pessimiste qu'un alias peut se produire * dans les cas où rien dans le code ne le suggérerait *, mais pas les compilateurs de qualité aliasing dans les cas où c'est évident, surtout sur les plateformes où cela pourrait être utile. Étant donné 'uint32_t float_as_bits (float * fp) {return * (uint32_t *) fp; } ', serait-il vraiment" pessimiste "pour un compilateur de présumer que la fonction pourrait accéder à quelque chose de type' float'? – supercat

+0

@supercat Peut-être. C'est certainement une nuisance. Malheureusement, c'est ainsi que le C++ va et C semble suivre. Sur le bon côté, la génération de code machine directement est toujours une option. –