Por que declarações sem efeito são consideradas legais em C?

13

Perdoe se esta pergunta é ingênua. Considere o seguinte programa:

#include <stdio.h>

int main() {
  int i = 1;
  i = i + 2;
  5;
  i;
  printf("i: %d\n", i);
}

No exemplo acima, as demonstrações 5;e i;parece totalmente supérfluo, mas os compila código sem avisos ou erros por padrão (no entanto, gcc faz lançar um warning: statement with no effect [-Wunused-value]aviso quando correu com -Wall). Eles não têm efeito sobre o restante do programa, então por que são considerados declarações válidas em primeiro lugar? O compilador simplesmente os ignora? Existem benefícios em permitir tais declarações?

AW
fonte
5
Quais são os benefícios de banir tais declarações?
Mooing Duck
2
Qualquer expressão pode ser uma declaração colocando-a ;depois dela. Seria complicado para o idioma adicionar mais regras sobre quando expressões não podem ser declarações
MM
3
Você prefere que seu código não seja compilado porque ignora o valor de retorno de printf()? A instrução 5;basicamente diz "faça o que 5fizer (nada) e ignore o resultado. Sua instrução printf(...)é" faça o que printf(...)fizer e ignore os resultados (o valor de retorno de printf()) ". C trata os mesmos. Isso também permite códigos como (void) i;onde iestá um parâmetro para uma função que você projeta para voidmarcá-lo como deliberadamente não utilizado
Andrew Henle
11
@AndrewHenle: Isso não é o mesmo, porque chamar printf()tem um efeito, mesmo que você ignore o valor que eventualmente retorna. Por outro lado 5;, não tem efeito algum.
Nate Eldredge
11
Porque Dennis Ritchie, e ele não está por perto para nos contar.
user207421 28/02

Respostas:

10

Um benefício em permitir tais declarações é do código criado por macros ou outros programas, em vez de ser escrito por humanos.

Como exemplo, imagine uma função int do_stuff(void)que deve retornar 0 em caso de sucesso ou -1 em caso de falha. Pode ser que o suporte a "coisas" seja opcional e, portanto, você possa ter um arquivo de cabeçalho que

#if STUFF_SUPPORTED
#define do_stuff() really_do_stuff()
#else
#define do_stuff() (-1)
#endif

Agora imagine um código que deseja fazer coisas, se possível, mas pode ou não se importar se é bem-sucedido ou falha:

void func1(void) {
    if (do_stuff() == -1) {
        printf("stuff did not work\n");
    }
}

void func2(void) {
    do_stuff(); // don't care if it works or not
    more_stuff();
}

Quando STUFF_SUPPORTEDfor 0, o pré-processador expandirá a chamada func2para uma instrução que apenas lê

    (-1);

e assim o passe do compilador verá exatamente o tipo de declaração "supérflua" que parece incomodá-lo. No entanto, o que mais se pode fazer? Se você #define do_stuff() // nothing, o código func1será quebrado. (E você ainda terá uma declaração vazia na func2qual apenas lê ;, o que talvez seja ainda mais supérfluo.) Por outro lado, se você precisar realmente definir uma do_stuff()função que retorne -1, poderá incorrer no custo de uma chamada de função por nenhuma boa razão.

Nate Eldredge
fonte
Uma versão mais clássica (ou refiro-me à versão comum) do no-op é ((void)0).
Jonathan Leffler
Um bom exemplo disso é assert.
Neil
3

Instruções simples em C são encerradas por ponto e vírgula.

Instruções simples em C são expressões. Uma expressão é uma combinação de variáveis, constantes e operadores. Toda expressão resulta em algum valor de um determinado tipo que pode ser atribuído a uma variável.

Dito isto, alguns "compiladores inteligentes" podem descartar 5; e eu; afirmações.

J. Dumbass
fonte
Não consigo imaginar nenhum compilador que faria algo com essas instruções além de descartá-las. O que mais isso poderia fazer com eles?
Jeremy Friesner 28/02
@ JeremyFriesner: Um compilador muito simples e sem otimização pode muito bem gerar código para calcular o valor e colocar o resultado em um registro (a partir do qual seria ignorado).
Nate Eldredge
O padrão C não inclui o termo "declaração simples". Uma declaração de expressão consiste em uma expressão (opcional) seguida de um ponto e vírgula. Nem toda expressão resulta em um valor; uma expressão do tipo voidnão tem valor.
Keith Thompson
2

Declarações sem efeito são permitidas porque seria mais difícil bani-las do que permitir. Isso foi mais relevante quando o C foi projetado pela primeira vez e os compiladores eram menores e mais simples.

Uma declaração de expressão consiste em uma expressão seguida por ponto e vírgula. Seu comportamento é avaliar a expressão e descartar o resultado (se houver). Normalmente, o objetivo é que a avaliação da expressão tenha efeitos colaterais, mas nem sempre é fácil ou mesmo possível determinar se uma determinada expressão tem efeitos colaterais.

Por exemplo, uma chamada de função é uma expressão; portanto, uma chamada de função seguida por um ponto e vírgula é uma instrução. Esta declaração tem algum efeito colateral?

some_function();

É impossível dizer sem ver a implementação de some_function.

Que tal agora?

obj;

Provavelmente não - mas se objé definido como volatile, então ele faz.

Permitir que qualquer expressão seja transformada em uma declaração de expressão adicionando um ponto-e-vírgula torna a definição da linguagem mais simples. Exigir que a expressão tenha efeitos colaterais adicionaria complexidade à definição da linguagem e ao compilador. C é construído sobre um conjunto consistente de regras (chamadas de função são expressões, atribuições são expressões, uma expressão seguida por ponto-e-vírgula é uma declaração) e permite que os programadores façam o que desejam, sem impedir que façam coisas que possam ou não fazer sentido.

Keith Thompson
fonte
2

As instruções listadas sem efeito são exemplos de uma expressão , cuja sintaxe é fornecida na seção 6.8.3p1 do padrão C , da seguinte maneira:

 expressão-declaração :
    expressão opt  ;

Toda a seção 6.5 é dedicada à definição de uma expressão, mas, em termos gerais, uma expressão consiste em constantes e identificadores vinculados aos operadores. Notavelmente, uma expressão pode ou não conter um operador de atribuição e pode ou não conter uma chamada de função.

Portanto, qualquer expressão seguida de ponto e vírgula se qualifica como uma declaração de expressão. De fato, cada uma dessas linhas do seu código é um exemplo de uma declaração de expressão:

i = i + 2;
5;
i;
printf("i: %d\n", i);

Alguns operadores contêm efeitos colaterais, como o conjunto de operadores de atribuição e os operadores de aumento / redução anteriores / posteriores, e o operador de chamada de função () pode ter um efeito colateral, dependendo do que a função em questão faz. No entanto, não é necessário que um dos operadores tenha um efeito colateral.

Aqui está outro exemplo:

atoi("1");

Isso está chamando uma função e descartando o resultado, assim como a chamada printfno seu exemplo, mas o contrário printfda chamada de função em si não tem um efeito colateral.

dbush
fonte
1

Às vezes, essas declarações são muito úteis:

int foo(int x, int y, int z)
{
    (void)y;   //prevents warning
    (void)z;

    return x*x;
}

Ou quando o manual de referência nos diz para ler apenas os registros para arquivar algo - por exemplo, para limpar ou definir alguma sinalização (situação muito comum no mundo dos EUA)

#define SREG   ((volatile uint32_t *)0x4000000)
#define DREG   ((volatile uint32_t *)0x4004000)

void readSREG(void)
{
    *SREG;   //we read it here
    *DREG;   // and here
}

https://godbolt.org/z/6wjh_5

P__J__
fonte
Quando *SREGé volátil, *SREG;não tem efeito no modelo especificado pelo padrão C. O padrão C especifica que ele tem um efeito colateral observável.
Eric Postpischil 28/02
@ EricPostpischil - não, não tem o efeito observável , mas se tem o efeito. Nenhum dos objetos visíveis C foi alterado.
P__J__
C 2018 5.1.2.3 6 define o comportamento observável do programa para incluir que "os acessos a objetos voláteis são avaliados estritamente de acordo com as regras da máquina abstrata". Não há questão de interpretação ou dedução; essa é a definição de comportamento observável.
Eric Postpischil 28/02