Optimisation inattendue de strlen lors de l'aliasage d'un tableau 2D

28

Voici mon code:

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

typedef char BUF[8];

typedef struct
{
    BUF b[23];
} S;

S s;

int main()
{
    int n;

    memcpy(&s, "1234567812345678", 17);

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

    n = strlen((char *)&s) / sizeof(BUF);
    printf("%d\n", n);
}

Utiliser gcc 8.3.0 ou 8.2.1 avec n'importe quel niveau d'optimisation sauf -O0, cela sort 0 2quand je m'y attendais 2 2. Le compilateur a décidé que le strlenest limité à b[0]et ne peut donc jamais égaler ou dépasser la valeur divisée par.

Est-ce un bug dans mon code ou un bug dans le compilateur?

Ce n'est pas clairement énoncé dans la norme, mais je pensais que l'interprétation générale de la provenance du pointeur était que pour tout objet X, le code (char *)&Xdevrait générer un pointeur qui peut itérer sur l'ensemble de X- ce concept devrait tenir même s'il Xse trouve sous-tableaux comme structure interne.

(Question bonus, existe-t-il un indicateur gcc pour désactiver cette optimisation spécifique?)

MM
la source
4
Ref: Mes rapports gcc 7.4.0 2 2sous diverses options.
chux
2
@Ale les garanties standard qu'elles sont à la même adresse (la structure ne peut pas avoir de remplissage initial)
MM
3
@ DavidRankin-ReinstateMonica "résultant en des limites de char (*) [8] étant limitées à b [0]. Mais c'est aussi loin que j'obtiens" Je pense que ça cloue. étant donné qu'il s.best limité, b[0]il est limité à 8 caractères, et donc deux options: (1) accès hors limite dans le cas où il y a 8 caractères non nuls, qui est UB, (2) il y a un caractère nul, dans lequel le len est inférieur à 8, donc la division par 8 donne zéro. Ainsi, la compilation du compilateur (1) + (2) peut utiliser l'UB pour donner le même résultat aux deux cas
user2162550
3
Étant donné que & s == & s.b, il n'y a aucun moyen que le résultat puisse différer. Comme l'a montré @ user2162550, strlen () n'est pas appelé et le compilateur jette une estimation de son résultat, même dans le cas godbolt.org/z/dMcrdy où le compilateur ne peut pas le savoir. Il s'agit d'un bug du compilateur .
Ale

Réponses:

-1

Il y a certains problèmes que je peux voir et ils peuvent être affectés par la façon dont le compilateur décide de disposer la mémoire.

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

Dans le code ci-dessus s.best un tableau de 23 entrées d'un tableau de 8 caractères. Lorsque vous vous référez simplement à s.bvous obtenez l'adresse de la première entrée dans le tableau de 23 octets (et le premier octet dans le tableau de 8 caractères). Lorsque le code dit &s.b, cela demande l'adresse de l'adresse du tableau. Sous les couvertures, le compilateur génère plus que probablement un stockage local, stockant l'adresse de la baie là-dedans et fournissant l'adresse du stockage local à strlen.

Vous avez 2 solutions possibles. Elles sont:

    n = strlen((char *)s.b) / sizeof(BUF);
    printf("%d\n", n);

ou

    n = strlen((char *)&s.b[0]) / sizeof(BUF);
    printf("%d\n", n);

J'ai également essayé d'exécuter votre programme et de démontrer le problème, mais clang et la version de gcc que j'ai avec toutes les -Ooptions fonctionnaient toujours comme prévu. Pour ce que ça vaut, j'utilise la version clang 9.0.0-2 et la version gcc 9.2.1 sur x86_64-pc-linux-gnu).

JonBelanger
la source
-2

Il y a des erreurs dans le code.

 memcpy(&s, "1234567812345678", 17);

par exemple, est risqué, même si s commence par b devrait être:

 memcpy(&s.b, "1234567812345678", 17);

Le deuxième strlen () contient également des erreurs

n = strlen((char *)&s) / sizeof(BUF);

par exemple, devrait être:

n = strlen((char *)&s.b) / sizeof(BUF);

La chaîne sb, si copiée correctement, doit comporter 17 lettres. Vous ne savez pas comment les structures sont stockées en mémoire, si elles sont alignées. Avez-vous vérifié que sb contient bien les 17 caractères copiés?

Un strlen (sb) devrait donc afficher 17

Le printf n'affiche que des nombres entiers, car% d est un entier et la variable n est déclarée comme un entier. sizeof (BUF), devrait être 8

Ainsi, un 17 divisé par 8 (17/8) devrait afficher 2 car n est déclaré entier. Comme memcpy était utilisé pour copier des données vers s et non vers sb, je suppose que cela a à voir avec les alignements de mémoire; en supposant qu'il s'agit d'un ordinateur 64 bits, il peut y avoir 8 caractères sur une adresse mémoire.

Par exemple, supposons que quelqu'un a appelé un malloc (1), que le prochain "espace libre" ne soit pas aligné ...

Le deuxième appel strlen, affiche le numéro correct, car la copie de la chaîne a été effectuée dans la structure s au lieu de sb

user413990
la source