web-dev-qa-db-fra.com

C comportement indéfini. Règle de crénelage stricte ou alignement incorrect?

Je ne peux pas expliquer le comportement d'exécution de ce programme:

#include <string> 
#include <cstdlib> 
#include <stdio.h>

typedef char u8;
typedef unsigned short u16;

size_t f(u8 *keyc, size_t len)
{
    u16 *key2 = (u16 *) (keyc + 1);
    size_t hash = len;
    len = len / 2;

    for (size_t i = 0; i < len; ++i)
        hash += key2[i];
    return hash;
}

int main()
{
    srand(time(NULL));
    size_t len;
    scanf("%lu", &len);
    u8 x[len];
    for (size_t i = 0; i < len; i++)
        x[i] = Rand();

    printf("out %lu\n", f(x, len));
}

Ainsi, lorsqu'il est compilé avec -O3 avec gcc et exécuté avec l'argument 25, il génère un segfault. Sans optimisation, cela fonctionne bien. Je l'ai désassemblé: il est vectorisé et le compilateur suppose que le tableau key2 est aligné sur 16 octets. Il utilise donc movdqa. Évidemment c'est UB, bien que je ne puisse pas l'expliquer. Je connais la règle de crénelage stricte et ce n'est pas le cas (j'espère), car, autant que je sache, la règle de crénelage strict ne fonctionne pas avec chars. Pourquoi gcc suppose-t-il que ce pointeur est aligné? Clang fonctionne bien aussi, même avec des optimisations.

MODIFIER

J'ai changé unsigned char en char et supprimé const, il segfa toujours par défaut.

EDIT2

Je sais que ce code n'est pas bon, mais il devrait bien fonctionner, autant que je sache à propos de la règle de crénelage stricte. Où est exactement la violation?

13
Nikita Vorobyev

Il est légal d'aliaser un pointeur sur un objet, puis sur tous les octets de l'objet d'origine.

Lorsqu'un pointeur sur char pointe en fait sur un objet (obtenu par le biais d'une opération précédente), il est légal de reconvertir en pointeur sur le type d'origine et la norme exige que vous récupériez la valeur d'origine.

Mais convertir un pointeur arbitraire en un caractère en un pointeur en objet et déréférencer le pointeur obtenu enfreint la règle de crénelage strict et invoque un comportement indéfini.

Donc, dans votre code, la ligne suivante est UB:

const u16 *key2 = (const u16 *) (keyc + 1); 
// keyc + 1 did not originally pointed to a u16: UB
6
Serge Ballesta

À moins que le code ne fasse quelque chose pour s'assurer qu'un tableau de type de caractère est aligné, il ne devrait pas particulièrement s'attendre à ce qu'il le soit.

Si l'alignement est pris en charge, si le code prend son adresse une fois, le convertit en un pointeur d'un autre type et n'accède jamais au stockage par un moyen ne dérivant pas de ce dernier, une implémentation conçue pour la programmation de bas niveau ne devrait pas avoir de particularité. difficulté à traiter le stockage comme un tampon abstrait. Dans la mesure où un tel traitement ne serait ni difficile ni nécessaire pour certains types de programmation de bas niveau (par exemple, la mise en place de pools de mémoire dans des contextes où malloc () pourrait ne pas être disponible), une implémentation qui ne prend pas en charge de tels concepts ne devrait pas prétendre être appropriée. pour la programmation de bas niveau.

Par conséquent, dans les implémentations conçues pour la programmation de bas niveau, des constructions telles que celle que vous décrivez permettraient de traiter des baies correctement alignées comme un stockage non typé. Malheureusement, il n’existe pas de moyen facile de reconnaître de telles implémentations, car celles qui sont conçues principalement pour la programmation de bas niveau ne répertorient souvent pas tous les cas où les auteurs penseraient qu’il est évident que ces implémentations se comportent de manière caractéristique de l’environnement ( et c’est précisément ce qu’ils font), alors que ceux dont la conception est axée sur d’autres objectifs peuvent prétendre être adaptés à une programmation de bas niveau même s’ils se comportent de manière inappropriée à cette fin.

Les auteurs de la norme reconnaissent que le C est un langage utile pour les programmes non portables, et ont spécifiquement déclaré qu'ils ne souhaitaient pas exclure son utilisation en tant qu '"assembleur de haut niveau". Ils s’attendaient toutefois à ce que les implémentations destinées à diverses applications prennent en charge les extensions courantes afin de faciliter ces utilisations, que la norme l’oblige ou non, et qu’il n’est donc pas nécessaire que la norme traite de telles choses. Cependant, comme cette intention était reléguée à la logique plutôt qu'à la norme, certains rédacteurs de compilateurs considèrent la norme comme une description complète de tout ce que les programmeurs doivent attendre d'une mise en œuvre, et peuvent donc ne pas prendre en charge des concepts de bas niveau tels que l'utilisation de la commande statique. - ou des objets à durée automatique en tant que tampons effectivement non typés.

0
supercat