Otimização inesperada de strlen ao criar alias em uma matriz 2-d

28

Aqui está o meu código:

#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);
}

Usando o gcc 8.3.0 ou 8.2.1 com qualquer nível de otimização, exceto -O0, isso gera 0 2quando eu estava esperando 2 2. O compilador decidiu que o strlenlimite é b[0]e, portanto, nunca pode ser igual ou exceder o valor dividido por.

Isso é um erro no meu código ou um erro no compilador?

Isso não está explicitado claramente no padrão, mas eu pensei que a interpretação principal da proveniência do ponteiro era que, para qualquer objeto X, o código (char *)&Xdeve gerar um ponteiro que possa iterar por todo o conjunto X- esse conceito deve se manter mesmo que Xaconteça sub-matrizes como estrutura interna.

(Pergunta de bônus, existe um sinalizador gcc para desativar essa otimização específica?)

MILÍMETROS
fonte
4
Ref: Meus relatórios do gcc 7.4.0 2 2sob várias opções.
chux - Restabelece Monica
2
@ Todas as garantias padrão de que estão no mesmo endereço (struct não pode ter preenchimento inicial) #
MM
3
@ DavidRankin-ReinstateMonica "resultando nos limites de char (*) [8] limitando-se a b [0]. Mas isso é o máximo que eu consigo", acho que isso é um problema. como s.bé limitado a b[0]8 caracteres e, portanto, duas opções: (1) acesso fora do limite, caso haja 8 caracteres não nulos, que é UB; (2) há um caractere nulo, no qual o len é menor que 8, portanto, dividir por 8 dá zero. Então, colocar juntos (1) + compilador (2) pode usar a UB para dar mesmo resultado para ambos os casos
user2162550
3
Dado que & s == & s.b, não há como o resultado ser diferente. Como o @ user2162550 mostrou, strlen () não é chamado e o compilador adivinha o resultado, mesmo no caso godbolt.org/z/dMcrdy em que o compilador não pode conhecê-lo. É um bug do compilador .
Ale

Respostas:

-1

Existem alguns problemas que eu posso ver e eles podem ser afetados pela maneira como o compilador decide colocar a memória em layout.

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

No código acima, s.bhá uma matriz de 23 entradas de uma matriz de 8 caracteres. Quando você se refere apenas s.ba obter o endereço da primeira entrada na matriz de 23 bytes (e o primeiro byte na matriz de 8 caracteres). Quando o código diz &s.b, isso está pedindo o endereço do endereço da matriz. Nos bastidores, é mais do que provável que o compilador gere algum armazenamento local, armazene o endereço da matriz e forneça o endereço do armazenamento local strlen.

Você tem 2 soluções possíveis. Eles são:

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

ou

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

Também tentei executar o seu programa e demonstrar o problema, mas o clang e a versão do gcc que tenho com todas as -Oopções ainda funcionavam conforme o esperado. Pelo que vale a pena, estou executando a versão 9.0.0-2 do clang e a versão 9.2.1 do gcc no x86_64-pc-linux-gnu).

JonBelanger
fonte
-2

Existem erros no código.

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

por exemplo, é arriscado, mesmo que s inicie com b deve ser:

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

O segundo strlen () também tem erros

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

por exemplo, deve ser:

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

A sequência sb, se copiada corretamente, deve ter 17 letras. Não tenho certeza de como as estruturas são armazenadas na memória, se estiverem alinhadas. Você verificou se sb realmente contém os 17 caracteres copiados?

Portanto, um strlen (sb) deve mostrar 17

O printf mostra apenas números inteiros, pois% d é inteiro e a variável n é declarada como inteiro. sizeof (BUF), deve ser 8

Portanto, um 17 dividido por 8 (17/8) deve imprimir 2, pois n é declarado como inteiro. Como o memcpy foi usado para copiar dados para se não para sb, eu acho que isso tem a ver com alinhamentos de memória; supondo que seja um computador de 64 bits, pode haver 8 caracteres em um endereço de memória.

Por exemplo, vamos supor que alguém tenha chamado um malloc (1), que o próximo "espaço livre" não esteja alinhado ...

A segunda chamada strlen mostra o número correto, pois a cópia da string foi feita na estrutura s em vez de sb

user413990
fonte