Alterando objeto const - sem aviso? Além disso, nesse caso, é UB?

9

Por que não há aviso no código a seguir?

int deserialize_students(const Student *dest, const int destCapacityMax)
{
    FILE *ptr_file;
    int i=0;


    ptr_file =fopen("output.txt","r");
    if (!ptr_file)
        return -1;

    if(destCapacityMax==0)
        return -2;

    while (!feof (ptr_file))
    {  
        fscanf (ptr_file, "%d", &dest[i].id);    // UB?
        fscanf (ptr_file, "%s",  dest[i].name);     
        fscanf (ptr_file, "%d", &dest[i].gender);      
        i++;

        if(i==destCapacityMax)
            return 0;

    }
    fclose(ptr_file);
    return 0;
 }

Foi assim que eu chamei:

Student students[5];
deserialize_students(students,5);

Também tenho a seguinte pergunta: é o que eu fiz comportamento indefinido? Nota:

  • Acho que passar students é bom porque a função espera const Student* e posso passar não const . Certo?
  • Mas quando modifico esse objeto em fscanf , aciono o UB? Ou isso depende se students foi declarado como const ou não em primeiro lugar (fora dessa função)?
por Grzegorz Szpetkowski 21.08.2015 в 09:05
fonte

4 respostas

2

Não há comportamento indefinido em seu código.

O que o chamador passou é um objeto mutável. Então, é bom modificá-lo diretamente ou por conversão explícita:

int func(const int *p) {
  int *q = (int*)p;
  *q = 5;
}

está bem (provavelmente não é aconselhável, mas legal) desde que o objeto passado para func() seja mutável.

Mas se o objeto passado foi const qualificado, então seria um comportamento indefinido. Então, no seu caso, não é indefinido.

O qualificador const é apenas um contrato que a função não deve modificar dest . Não tem influência na mutabilidade real de um objeto. Portanto, a modificação de um comando const invocado pelo UB ou não depende se o objeto transmitido possui tal qualificador.

Quanto ao aviso, o GCC (5.1.1) avisa para:

int func(const int *p) {
   fscanf(stdin, "%d", p);
}

com:

warning: writing into constant object (argument 3) [-Wformat=]

Provavelmente, o VS não reconhece que fscanf() modifica o objeto. Mas o padrão C apenas diz que ele é indefinido se você modificar um objeto com qualificação constante:

(rascunho C11, 6.7.3, qualificadores de 6 tipos)

  

Se for feita uma tentativa de modificar um objeto definido com um   tipo com qualificação constante por meio do uso de um lvalue com não-qualificado para const   tipo, o comportamento é indefinido.

Não há diagnóstico exigido pelo padrão C se o código invocar um comportamento indefinido. Em geral, você está por conta própria se seu código causar o UB e um compilador não puder ajudá-lo em todas as causas.

    
por P.P. 21.08.2015 / 10:07
fonte
1

IMHO é UB formal.

A função deserialize_students declara um parâmetro const Student *dest . A partir daí, dest[i].id é uma const int e &dest[i].id é uma const int * .

Você não recebe nenhum aviso, porque fscanf é uma função variádica e o compilador não pode controlar constness (mesmo se o gcc usá-lo como um caso especial) mas se você usasse uma variável temporária intermediária você obteria um erro:

int id;
fscanf (ptr_file, "%d", &id);
dest[i].id = id;       // here you get an error

Então você está passando um ponteiro const para uma função que modifica o pointee ( fscanf ) e IMHO é o suficiente para qualificá-lo como UB formal. Pode-se imaginar uma implementação de um compilador que passaria um ponteiro para uma cópia do valor para fscanf , desde que você prometeu que era const. Ou que teria passado um ponteiro para uma cópia da matriz students , pois deserialize_students declara seu parâmetro como const.

Existe um risco real? IMHO não porque como você passar um dest modificável para a função, a implementação normal do compilador apenas passará o endereço original, e o mesmo passará o endereço de dest[i].id to fscanf . Assim, a coisa toda terminará modificando corretamente o array original. Mas como já foi dito por Peter, como todas as instâncias de comportamento indefinido, um resultado possível está funcionando como você espera , então trabalhar com todos os compiladores testados não é seguro para não ser um comportamento indefinido.

NB: como o array original não é const, o objeto não foi definido como const, então não tenho certeza se 6.7.3, § 6 se aplica aqui. Mas 6.7.3 § 9 ainda diz: Para dois tipos qualificados serem compatíveis, ambos devem ter a versão com qualificação idêntica de um tipo compatível , então int * (requerido por fscanf), e const int * ( realmente passou) não são.

    
por Serge Ballesta 21.08.2015 / 11:08
fonte
0

O padrão não está claro sobre o assunto. De C11 7.21.6.2/12:

  

Os especificadores de conversão e seus significados são:

     

d Corresponde a um inteiro decimal opcionalmente assinado, cujo formato é o mesmo esperado para a sequência de assunto da função strtol com o valor 10   para o argumento base. O argumento correspondente será um ponteiro para   inteiro assinado .

Agora, você poderia argumentar que, como ele não diz "um ponteiro para um inteiro com sinal não const", na verdade, significa que int * e const int * estão OK. Ou você poderia argumentar que eles significam apenas um ponteiro para um inteiro não assinado.

Observe que a definição de vararg passing (7.16) diz explicitamente que é indefinido usar va_arg(int *) para recuperar um argumento do tipo const int * . No entanto, a função fscanf NÃO é especificada para se comportar como se va_arg tivesse sido usado. Então isso não é diretamente relevante.

A opinião segue: Toda a especificação de printf e scanf é uma bagunça desleixada que está abaixo da qualidade usual de especificação encontrada no restante do padrão. Existem muitos outros exemplos, por exemplo de acordo com as palavras exatas do padrão, printf("%lx", 1L); é um comportamento indefinido, enquanto printf("%lx", -1L); não é um comportamento indefinido.

Na realidade, as implementações tomaram suas próprias decisões sobre o que fazer e ninguém quer tocar o texto padrão com um polo de barcaça. Então, eu diria que não há realmente uma resposta correta para essa pergunta.

    
por M.M 21.11.2017 / 07:19
fonte
-1

É um comportamento indefinido.

Como Michael Walz mencionou nos comentários, o motivo pelo qual o compilador aceitou é que fscanf() é variádico, então certas verificações de compilador - incluindo constness - são relaxadas.

Como todas as ocorrências de comportamento indefinido, um resultado possível está funcionando como esperado - com o compilador e a biblioteca escolhidos, mas não necessariamente com o outro. É por isso que o teste não é um meio confiável de identificar a presença de comportamento indefinido - passar em qualquer teste também é permitido.

    
por Peter 21.08.2015 / 09:56
fonte