Question reinterpret_cast, char * et comportement indéfini


Quels sont les cas où reinterpret_casten a char* (ou char[N]) le comportement indéfini, et quand définit-il le comportement? Quelle est la règle générale que je devrais utiliser pour répondre à cette question?


Comme nous avons appris de cette question, le comportement suivant n'est pas défini:

alignas(int) char data[sizeof(int)];
int *myInt = new (data) int;           // OK
*myInt = 34;                           // OK
int i = *reinterpret_cast<int*>(data); // <== UB! have to use std::launder

Mais à quel point pouvons-nous faire un reinterpret_cast sur un char tableau et n'est-ce pas un comportement indéfini? Voici quelques exemples simples:

  1. Non new, juste reinterpret_cast:

    alignas(int) char data[sizeof(int)];
    *reinterpret_cast<int*>(data) = 42;    // is the first cast write UB?
    int i = *reinterpret_cast<int*>(data); // how about a read?
    *reinterpret_cast<int*>(data) = 4;     // how about the second write?
    int j = *reinterpret_cast<int*>(data); // or the second read?
    

    Quand la vie pour le int début? Est-ce avec la déclaration de data? Si oui, quand la vie de data fin?

  2. Et qu'est-ce qui se passerait si data étaient un pointeur?

    char* data_ptr = new char[sizeof(int)];
    *reinterpret_cast<int*>(data_ptr) = 4;     // is this UB?
    int i = *reinterpret_cast<int*>(data_ptr); // how about the read?
    
  3. Que se passe-t-il si je ne reçois que des structures sur le câble et que vous souhaitez les conditionner en fonction du premier octet?

    // bunch of handle functions that do stuff with the members of these types
    void handle(MsgType1 const& );
    void handle(MsgTypeF const& );
    
    char buffer[100]; 
    ::recv(some_socket, buffer, 100)
    
    switch (buffer[0]) {
    case '1':
        handle(*reinterpret_cast<MsgType1*>(buffer)); // is this UB?
        break;
    case 'F':
        handle(*reinterpret_cast<MsgTypeF*>(buffer));
        break;
    // ...
    }
    

Certains de ces cas sont-ils UB? Sont-ils tous? Est-ce que la réponse à cette question change entre C ++ 11 et C ++ 1z?


12
2017-09-10 18:51


origine


Réponses:


Il y a deux règles en jeu ici:

  1. [basic.lval] / 8, aka, la règle stricte d'aliasing: en termes simples, vous ne pouvez pas accéder à un objet via un pointeur / référence sur le mauvais type.

  2. [base.life] / 8: en termes simples, si vous réutilisez le stockage pour un objet d'un type différent, vous ne pouvez pas utiliser les pointeurs vers les anciens objets sans les blanchir en premier.

Ces règles jouent un rôle important dans la distinction entre "un emplacement de mémoire" ou "une région de stockage" et "un objet".

Tous vos exemples de code sont en proie au même problème: ils ne sont pas l'objet auquel vous les envoyez:

alignas(int) char data[sizeof(int)];

Cela crée un objet de type char[sizeof(int)]. Cet objet est ne pas un int. Par conséquent, vous ne pouvez pas y accéder comme si c'était le cas. Peu importe que ce soit une lecture ou une écriture; vous provoquez toujours UB.

De même:

char* data_ptr = new char[sizeof(int)];

Cela crée également un objet de type char[sizeof(int)].

char buffer[100];

Cela crée un objet de type char[100]. Cet objet n'est ni un MsgType1 ni un MsgTypeF. Donc, vous ne pouvez pas y accéder comme si c'était soit.

Notez que la UB est ici lorsque vous accédez au tampon en tant qu'un des Msg* types, pas lorsque vous vérifiez le premier octet. Si tout votre Msg* les types sont trivialement copiables, il est parfaitement acceptable de lire le premier octet, puis de copier le tampon dans un objet du type approprié.

switch (buffer[0]) {
case '1':
    {
        MsgType1 msg;
        memcpy(&msg, buffer, sizeof(MsgType1);
        handle(msg);
    }
    break;
case 'F':
    {
        MsgTypeF msg;
        memcpy(&msg, buffer, sizeof(MsgTypeF);
        handle(msg);
    }
    break;
// ...
}

Notez que nous parlons de ce que les états du langage seront un comportement indéfini. Il y a de bonnes chances que le compilateur soit parfait avec l'un de ces éléments.

Est-ce que la réponse à cette question change entre C ++ 11 et C ++ 1z?

Il y a eu une règle importante clarifications depuis C ++ 11 (en particulier [basic.life]). Mais l'intention derrière les règles n'a pas changé.


4
2017-09-11 01:59