que - qual a diferença entre i++ e++i em c




Existe uma diferença de desempenho entre i++ e++ i em C? (9)

@Mark Mesmo que o compilador tenha permissão para otimizar a cópia temporária (baseada em pilha) da variável e gcc (em versões recentes) está fazendo isso, não significa que todos os compiladores sempre farão isso.

Eu apenas testei com os compiladores que usamos em nosso projeto atual e 3 de 4 não o otimizam.

Nunca assuma que o compilador acerta, especialmente se o código possivelmente mais rápido, mas nunca mais lento, é tão fácil de ler.

Se você não tiver uma implementação realmente estúpida de um dos operadores em seu código:

Alwas preferem ++ i sobre i ++.

Existe uma diferença de desempenho entre i++ e ++i se o valor resultante não for usado?


Aqui está uma observação adicional se você estiver preocupado com a otimização micro. Loops decrescentes podem 'possivelmente' ser mais eficientes do que incrementar loops (dependendo da arquitetura do conjunto de instruções, por exemplo, ARM), dado:

for (i = 0; i < 100; i++)

Em cada loop você terá uma instrução cada para:

  1. Adicionando 1 a i .
  2. Compare se i é menor que 100 .
  3. Um ramo condicional se i for menor que 100 .

Considerando um loop de decrementação:

for (i = 100; i != 0; i--)

O loop terá uma instrução para cada um dos seguintes:

  1. Decrementar i , configurando o sinalizador de status do registro da CPU.
  2. Um ramo condicional dependendo do status do registro da CPU ( Z==0 ).

Claro que isso funciona apenas quando decrementar a zero!

Lembrado do Guia do desenvolvedor do sistema ARM.


Em C, o compilador geralmente pode otimizá-los para serem os mesmos se o resultado não for utilizado.

No entanto, em C ++, se estiver usando outros tipos que forneçam seus próprios operadores ++, a versão do prefixo provavelmente será mais rápida que a versão do postfix. Então, se você não precisa da semântica do postfix, é melhor usar o operador prefix.


Eu posso pensar em uma situação em que o postfix é mais lento que o incremento de prefixo:

Imagine que um processador com registrador A seja usado como acumulador e seja o único registro usado em muitas instruções (alguns microcontroladores pequenos são realmente assim).

Agora imagine o seguinte programa e sua tradução em uma montagem hipotética:

Incremento de prefixo:

a = ++b + c;

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

Incremento de postfix:

a = b++ + c;

; load b
LD    A, [&b]

; add with c
ADD   A, [&c]

; store in a
ST    A, [&a]

; increment b
LD    A, [&b]
INC   A
ST    A, [&b]

Observe como o valor de b foi forçado a ser recarregado. Com o incremento de prefixo, o compilador pode apenas incrementar o valor e ir em frente com o uso, possivelmente evitando recarregá-lo já que o valor desejado já está no registrador após o incremento. No entanto, com o incremento de postfix, o compilador tem que lidar com dois valores, um o antigo e um o valor incrementado que, como mostrado acima, resulta em mais um acesso à memória.

Claro, se o valor do incremento não for usado, como um único i++; instrução, o compilador pode (e simplesmente) gerar uma instrução de incremento independentemente do uso do prefixo ou do prefixo.

Como uma nota secundária, gostaria de mencionar que uma expressão na qual há um b++ não pode simplesmente ser convertida em uma com ++b sem nenhum esforço adicional (por exemplo, adicionando a - 1 ). Então, comparar os dois se eles fazem parte de alguma expressão não é realmente válido. Muitas vezes, quando você usa b++ dentro de uma expressão, você não pode usar ++b , então, mesmo se ++b fosse potencialmente mais eficiente, seria simplesmente errado. A exceção é, claro, se a expressão está implorando por ela (por exemplo, a = b++ + 1; que pode ser alterada para a = ++b; ).


Meu C está um pouco enferrujado, por isso peço desculpas antecipadamente. Speedwise, eu posso entender os resultados. Mas, estou confuso sobre como ambos os arquivos saíram para o mesmo hash MD5. Talvez um loop for execute o mesmo, mas as duas linhas de código a seguir não gerariam uma montagem diferente?

myArray[i++] = "hello";

vs

myArray[++i] = "hello";

O primeiro escreve o valor na matriz e, em seguida, incrementa i. O segundo incrementa então escrevo para o array. Não sou especialista em assembly, mas não vejo como o mesmo executável seria gerado por essas duas linhas de código diferentes.

Apenas meus dois centavos.


Por favor, não deixe a questão de "qual é mais rápido" ser o fator decisivo de qual usar. As chances são de que você nunca vai se importar tanto, e além disso, o tempo de leitura do programador é muito mais caro do que o tempo da máquina.

Use o que mais fizer sentido para o humano ler o código.


Resumo executivo: Não.

i++ poderia ser mais lento que ++i , já que o antigo valor de i talvez precise ser salvo para uso posterior, mas, na prática, todos os compiladores modernos irão otimizar isso.

Podemos demonstrar isso observando o código dessa função, tanto com o ++i quanto com o i++ .

$ cat i++.c
extern void g(int i);
void f()
{
    int i;

    for (i = 0; i < 100; i++)
        g(i);

}

Os arquivos são os mesmos, exceto para ++i e i++ :

$ diff i++.c ++i.c
6c6
<     for (i = 0; i < 100; i++)
---
>     for (i = 0; i < 100; ++i)

Vamos compilá-los e também obter o montador gerado:

$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c

E podemos ver que o objeto gerado e os arquivos do assembler são os mesmos.

$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22

Tomando uma folha de Scott Meyers, Mais Eficaz c ++ Item 6: Distinguir entre as formas de incremento e decremento do prefixo e do postfix .

A versão do prefixo é sempre preferível ao postfix em relação aos objetos, especialmente no que diz respeito aos iteradores.

A razão para isso, se você olhar para o padrão de chamadas dos operadores.

// Prefix
Integer& Integer::operator++()
{
    *this += 1;
    return *this;
}

// Postfix
const Integer Integer::operator++(int)
{
    Integer oldValue = *this;
    ++(*this);
    return oldValue;
}

Olhando para este exemplo, é fácil ver como o operador prefix sempre será mais eficiente que o postfix. Por causa da necessidade de um objeto temporário no uso do postfix.

É por isso que quando você vê exemplos usando iteradores, eles sempre usam a versão do prefixo.

Mas, como você aponta para int, não há diferença efetiva por causa da otimização do compilador que pode ocorrer.


Resposta curta:

Nunca há qualquer diferença entre i++ e ++i em termos de velocidade. Um bom compilador não deve gerar código diferente nos dois casos.

Resposta longa:

O que todas as outras respostas não mencionam é que a diferença entre ++i versus i++ só faz sentido dentro da expressão que é encontrada.

No caso de for(i=0; i<n; i++) , o i++ está sozinho em sua própria expressão: existe um ponto de sequência antes do i++ e há um após ele. Assim, o único código de máquina gerado é "aumentar i por 1 " e é bem definido como isso é sequenciado em relação ao resto do programa. Então, se você mudasse para prefixo ++ , não importaria nem um pouco, você ainda teria o código de máquina "aumentar i por 1 ".

As diferenças entre ++i e i++ só importam em expressões como array[i++] = x; versus array[++i] = x; . Alguns podem argumentar e dizer que o postfix será mais lento em tais operações, porque o registro onde i reside precisa ser recarregado mais tarde. Mas então note que o compilador está livre para ordenar suas instruções da maneira que lhe agrada, contanto que não "quebre o comportamento da máquina abstrata" como o padrão C a chama.

Então, enquanto você pode assumir que array[i++] = x; é traduzido para código de máquina como:

  • Armazene o valor de i no registro A.
  • Armazene o endereço da matriz no registro B.
  • Adicione A e B, armazene os resultados em A.
  • Neste novo endereço representado por A, armazene o valor de x.
  • Armazene o valor de i no registrador A // ineficiente porque a instrução extra aqui, nós já fizemos isso uma vez.
  • Registrador de incremento A.
  • Armazene o registrador A em i .

o compilador pode também produzir o código com mais eficiência, como:

  • Armazene o valor de i no registro A.
  • Armazene o endereço da matriz no registro B.
  • Adicione A e B, armazene os resultados em B.
  • Registrador de incremento A.
  • Armazene o registrador A em i .
  • ... // resto do código.

Só porque você como um programador C é treinado para pensar que o postfix ++ acontece no final, o código da máquina não precisa ser ordenado dessa forma.

Portanto, não há diferença entre prefixo e postfix ++ em C. Agora, o que você como um programador C deve ser, é de pessoas que usam inconsistentemente o prefixo em alguns casos e postfix em outros casos, sem qualquer razão por que. Isso sugere que eles não estão certos sobre como o C funciona ou se possuem um conhecimento incorreto do idioma. Este é sempre um mau sinal, por sua vez, sugere que eles estão fazendo outras decisões questionáveis ​​em seu programa, com base na superstição ou "dogmas religiosos".

"Prefix ++ é sempre mais rápido" é de fato um desses dogmas falsos que é comum entre os futuros programadores C.





pre-increment