C ++에서 i ++와 ++ i간에 성능 차이가 있습니까?


352

우리는 질문을 한 사이의 성능 차이가 i++++i C의는 ?

C ++에 대한 답은 무엇입니까?


이 두 태그가이 특성에 대한 질문을 찾는 가장 쉬운 방법이기 때문에 다시 태그를 추가했습니다. 또한 응집력있는 태그가없는 다른 사람들을 통해 응집력있는 태그를 부여했습니다.
George Stocker

104
C ++과 ++ C 사용간에 성능 차이가 있습니까?
new123456

2
기사 : 반복자에 접미사 연산자 it ++ 대신 접두사 증가 연산자 ++ it를 사용하는 것이 합리적입니까? - viva64.com/en/b/0093

답변:


426

[실행 요약 : 사용할 ++i특별한 이유가없는 경우 사용 i++]

C ++의 경우 대답이 조금 더 복잡합니다.

경우 i, 간단한 유형 (안 C ++ 클래스의 인스턴스)입니다 다음 C에 주어진 답은 ( "아니 성능에 차이가") 컴파일러가 코드를 생성하기 때문에, 보유하지 않습니다.

그러나, 경우는 iC ++ 클래스의 인스턴스는, 인 i++++i중 하나를 호출하고 있습니다 operator++기능. 이러한 기능의 표준 쌍은 다음과 같습니다.

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

컴파일러는 코드를 생성하지 않고 operator++함수를 호출하기 때문에 tmp변수와 관련 복사 생성자 를 최적화 할 수있는 방법이 없습니다 . 복사 생성자가 비싸면 성능에 상당한 영향을 줄 수 있습니다.


3
컴파일러가 피할 수있는 것은 다른 주석에서 언급 한 것처럼 NRVO를 통해 호출자에 tmp를 할당하여 tmp를 리턴하는 두 번째 사본입니다.
Blaisorblade

7
operator ++가 인라인 된 경우 컴파일러에서이를 피할 수 없습니까?
에두아르-가브리엘 문테 아누

16
예. operator ++가 인라인되고 tmp가 사용되지 않는 경우 tmp 객체의 생성자 또는 소멸자가 부작용이없는 한 제거 할 수 있습니다.
Zan Lynx

5
@kriss : C와 C ++의 차이점은 C에서 연산자가 인라인 될 것이라는 보장이 있으며 그 시점에서 적절한 최적화 프로그램이 차이를 제거 할 수 있다는 것입니다. 대신 C ++에서는 인라인을 가정 할 수 없습니다. 항상 그런 것은 아닙니다.
Blaisorblade

3
대답이 자동 생성, 스마트 또는 원시 메모리에 대한 포인터를 동적으로 할당 된 (힙) 메모리에 보유하는 클래스에 대해 뭔가 언급했다면 복사 생성자는 반드시 깊은 복사를 수행합니다. 그러한 경우에는 논쟁이 없습니다. ++ i는 아마도 i ++보다 훨씬 효율적입니다. 그것들의 핵심은 사후 증분 시맨틱이 실제로 알고리즘에 필요하지 않을 때마다 사전 증분을 사용하는 습관을 얻는 것입니다. 그러면 방법에 관계없이 자연스럽게 더 큰 효율성을 제공하는 코드를 작성하는 습관에 처하게됩니다 잘 컴파일러가 최적화 할 수 있습니다.
phonetagger

64

예. 있습니다.

++ 연산자는 함수로 정의되거나 정의되지 않을 수 있습니다. 기본 유형 (int, double, ...)의 경우 연산자가 내장되어 있으므로 컴파일러가 코드를 최적화 할 수 있습니다. 그러나 ++ 연산자를 정의하는 객체의 경우 상황이 다릅니다.

operator ++ (int) 함수는 사본을 작성해야합니다. postfix ++는 보유하고있는 것과 다른 값을 반환해야하기 때문입니다. 임시 변수에 값을 보유하고 값을 증가시킨 다음 온도를 반환해야합니다. 접두사 ++ 인 operator ++ ()의 경우 복사본을 만들 필요가 없습니다. 개체는 자체적으로 증가한 다음 간단히 반환 할 수 있습니다.

다음은 요점을 보여줍니다.

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

operator ++ (int)를 호출 할 때마다 사본을 작성해야하며 컴파일러는 이에 대해 아무것도 수행 할 수 없습니다. 선택권이 주어지면 operator ++ (); 이 방법으로 사본을 저장하지 않습니다. 많은 증분 (큰 루프?) 및 / 또는 큰 객체의 경우 중요 할 수 있습니다.


2
"사전 증분 연산자는 코드에 데이터 종속성을 도입합니다. CPU는 증분 연산이 완료 될 때까지 기다려야 해당 값을 표현식에 사용할 수 있습니다. 파이프 라인이 깊은 CPU에서는 중단이 발생합니다. 데이터 종속성이 없습니다. 사후 증분 연산자 ( 게임 엔진 아키텍처 (제 2 판) ) 따라서 포스트 증분의 사본이 계산 집약적이지 않더라도 여전히 프리 증분을 이길 수 있습니다.
Matthias

접미사 코드에서 어떻게 작동합니까? C t(*this); ++(*this); return t;두 번째 줄에서는이 포인터를 오른쪽으로 늘리는 것이므로이를 늘리면 어떻게 t업데이트됩니까? 이것의 값이 이미 복사되지 않았습니까 t?
rasen58

The operator++(int) function must create a copy.전혀 그렇지 않다. 더 이상 사본operator++()
Severin Pappadeux

47

증분 연산자가 다른 변환 단위에있는 경우에 대한 벤치 마크입니다. g ++ 4.5가 포함 된 컴파일러

지금은 스타일 문제를 무시하십시오.

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}

O (n) 증분

테스트

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

결과

가상 머신에서 g ++ 4.5를 사용한 결과 (시간은 초 단위 임) :

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82

O (1) 증분

테스트

이제 다음 파일을 보자.

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

증분에서는 아무 것도 수행하지 않습니다. 증분의 복잡성이 일정한 경우를 시뮬레이션합니다.

결과

결과는 이제 매우 다양합니다.

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90

결론

성능 측면

이전 값이 필요하지 않은 경우 사전 증분을 사용하는 습관을들이십시오. 내장 유형과 일관성이 유지되면 익숙해지며 내장 유형을 사용자 정의 유형으로 교체하더라도 불필요한 성능 손실의 위험이 없습니다.

의미 론적으로

  • i++말한다 increment i, I am interested in the previous value, though.
  • ++iincrement i, I am interested in the current value또는 말한다 increment i, no interest in the previous value. 다시 말하지만 지금 당장은 아니더라도 익숙해 질 것입니다.

크 누스.

조기 최적화는 모든 악의 근원입니다. 조기 비관 화도 마찬가지입니다.


1
재미있는 시험. 거의 2 년 반이 지난 지금 gcc 4.9와 Clang 3.4도 비슷한 경향을 보입니다. Clang은 둘 다 빠르지 만 prefix와 postfix의 차이는 gcc보다 나쁩니다.
씹는 양말

내가 정말로보고 싶은 것은 ++ i / i ++가 차이를 만드는 실제 예입니다. 예를 들어 std 반복자에 차이가 있습니까?
Jakob Schou Jensen 2016 년

@JakobSchouJensen : 이것들은 실제 예제가 될 것입니다. SIMD 하드웨어에서 데이터 처리량을 최대화하기 위해 복잡한 트리 구조 (예 : kd-tree, quad-trees) 또는 표현식 템플릿에 사용되는 대형 컨테이너가있는 대규모 응용 프로그램을 고려하십시오. 그것이 차이가 있다면, 의미 론적으로 필요하지 않은 경우 특정 사례에 대해 사후 증가로 대체되는 이유가 확실하지 않습니다.
Sebastian Mach

@ phresnel : 나는 operator ++가 일상적으로 표현 템플릿이라고 생각하지 않습니다. 실제 예제가 있습니까? 연산자 ++의 일반적인 사용법은 정수와 반복자입니다. 그것은 차이가 있는지 아는 것이 흥미로울 것이라고 생각했습니다 (물론 정수에는 차이가 없지만 반복자).
Jakob Schou Jensen 2016

@JakobSchouJensen : 실제 비즈니스 사례는 없지만 물건을 세는 몇 가지 응용 프로그램이 있습니다. wrt 반복자는 관용적 C ++ 스타일로 작성된 광선 추적기를 고려 for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }하고 실제 트리 구조자 (BSP, kd, Quadtree, Octree Grid 등)를 신경 쓰지 않는 깊이 우선 순회를위한 반복자를 가지고 있습니다 . 이러한 반복자는 어떤 상태, 예를 들면 유지해야 parent node, child node, index같은 물건을. 대체로, 몇 가지 사례 만 존재하더라도 내 입장은 ...
Sebastian Mach

20

컴파일러가 접미사 경우 임시 변수 사본을 최적화 할 수 없다고 말하는 것은 전적으로 올바르지 않습니다. VC를 사용한 빠른 테스트는 적어도 어떤 경우에는 그렇게 할 수 있음을 보여줍니다.

다음 예에서 생성 된 코드는 예를 들어 접두사와 접미사에 동일합니다.

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

++ testFoo이든 testFoo ++이든 관계없이 여전히 동일한 결과 코드를 얻게됩니다. 실제로, 사용자로부터 카운트를 읽지 않아도 옵티마이 저는 모든 것을 일정하게 유지했습니다. 그래서 이건:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

결과는 다음과 같습니다.

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

따라서 접미사 버전이 느려질 수는 있지만 최적화 프로그램을 사용하지 않으면 임시 사본을 제거하기에 충분할 수 있습니다.


8
여기에 모든 것이 인라인되어 있다는 중요한 점을 잊어 버렸습니다. 연산자의 정의를 사용할 수 없으면 라인 외부 코드에서 수행 된 복사를 피할 수 없습니다. 최적화를 인라인하면 분명히 알 수 있으므로 모든 컴파일러가 그렇게합니다.
Blaisorblade

14

구글 C ++ 스타일 가이드는 말한다 :

사전 증가와 사전 감소

반복자 및 기타 템플릿 객체와 함께 증분 및 감소 연산자의 접두사 형식 (++ i)을 사용하십시오.

정의 : 변수가 증가 (++ i 또는 i ++) 또는 감소 (--i 또는 i--)되고 식의 값이 사용되지 않는 경우 사전 증가 (감소) 또는 사후 증가 (감소) 여부를 결정해야합니다.

장점 : 반환 값을 무시하면 "pre"형식 (++ i)이 "post"형식 (i ++)보다 효율적이지 않으며 종종 더 효율적입니다. 증가 후 (또는 감소) i의 사본을 작성해야하므로 표현식 값이 필요하기 때문입니다. i가 반복자이거나 다른 스칼라가 아닌 유형 인 경우 i를 복사하면 비용이 많이들 수 있습니다. 값이 무시 될 때 두 유형의 증분이 동일하게 동작하므로 항상 사전 증분하지 않는 이유는 무엇입니까?

단점 : C에서, 특히 for 루프에서 표현식 값이 사용되지 않을 때 후행 증가를 사용하는 전통이 개발되었습니다. "대상"(i)이 영어 에서처럼 "동사"(++)보다 앞에 있기 때문에 일부는 증가 후 사후를 읽기가 더 쉽다는 것을 알게됩니다.

결정 : 간단한 스칼라 (객체가 아닌) 값의 경우 하나의 형식을 선호 할 이유가 없으며 둘 중 하나를 허용합니다. 반복자 및 기타 템플릿 유형의 경우 사전 증가를 사용하십시오.


1
"결정 : 간단한 스칼라 (객체가 아닌) 값의 경우 하나의 형식을 선호 할 이유가 없으며 두 가지 중 하나를 허용합니다. 반복자와 다른 템플릿 유형의 경우 사전 증가를 사용하십시오."
Nosredna

2
어, ... 그리고 그게 뭐야?
Sebastian Mach

대답에 언급 된 링크는 현재 고장
났습니다

4

Andrew Koenig가 Code Talk에 대한 훌륭한 게시물을 최근에 지적하고 싶습니다.

http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29

우리 회사는 또한 해당되는 경우 일관성과 성능을 위해 ++ iter 규칙을 사용합니다. 그러나 Andrew는 의도와 성능에 대해 간과 된 세부 사항을 제시합니다. 우리가 ++ iter 대신 iter ++를 ​​사용하고 싶을 때가 있습니다.

따라서 먼저 의도를 결정하고 사전 또는 사후가 중요하지 않은 경우 추가 객체 생성을 피하고 던지므로 성능상의 이점이 있으므로 pre를 사용하십시오.



4

K

... 의도 대 성능에 대해 간과 된 세부 사항을 제기합니다. 우리가 ++ iter 대신 iter ++를 ​​사용하고 싶을 때가 있습니다.

분명히 post와 pre-increment는 다른 의미를 지니고 있으며 결과가 사용될 때 적절한 연산자를 사용해야한다는 것에 모든 사람이 동의합니다. 문제는 결과가 폐기 될 때 ( for루프 에서와 같이)해야 할 일이라고 생각합니다 . 질문 (IMHO)에 대한 답 은 성능 고려 사항이 무시할 수 있으므로보다 자연스러운 것을 수행해야한다는 것입니다. 나 자신 ++i은 더 자연 스럽지만 내 경험에 따르면 소수에 있고 사용 i++하면 코드를 읽는 대부분의 사람들에게 금속 오버 헤드가 줄어 듭니다 .

결국 그 언어는 " ++C" 라고 불리지 않습니다 . [*]

[*] ++C더 논리적 인 이름 에 대한 필수 토론을 삽입하십시오 .


4
@Motti : (joking) Bjarne Stroustrup C ++가 처음에 C 프로그램을 생성하는 사전 컴파일러로 코딩 한 것을 상기하면 C ++ 이름은 논리적입니다. 따라서 C ++은 이전 C 값을 반환했습니다. 또는 C ++이 처음부터 개념적으로 결함이 있음을 향상시킬 수 있습니다.
kriss

4
  1. ++ i- 반환 값을 사용하지 않는 것이 더 빠름
  2. i ++ - 반환 값을 사용 하여 더 빠르게

사용하지 않는 반환 값을 컴파일러의 경우 임시을 사용하지 않도록 보장 ++ 전 . 더 빠르다는 보장은 없지만 느리지는 않습니다.

경우 사용 반환 값을 난 ++ 서로에 의존하지 않기 때문에, 상기 프로세서는 상기 파이프 라인으로 증가 및 좌측 모두 푸시 할 수있다. ++ i는 증분 작업이 끝날 때까지 프로세서가 왼쪽을 시작할 수 없기 때문에 파이프 라인을 중단시킬 수 있습니다. 프로세서가 다른 유용한 기능을 찾을 수 있기 때문에 파이프 라인 스톨이 보장되지 않습니다.


3

Mark : operator ++가 인라인 될 수있는 좋은 후보임을 지적하고 싶었습니다. 컴파일러가 그렇게하기로 선택하면 대부분의 경우 중복 사본이 제거됩니다. (예 : 반복자가 일반적으로 사용하는 POD 유형)

즉, 대부분의 경우 ++ iter를 사용하는 것이 여전히 더 좋습니다. :-)


3

사이의 성능 차이 ++i와는 i++당신이 값을 돌려 함수로 연산자로 생각하고 그들이 어떻게 구현 될 때 더 명백 할 것이다. 무슨 일이 일어나고 있는지 이해하기 쉽게하기 위해 다음 코드 예제는 int마치struct .

++i변수를 증가시킨 다음 결과 반환합니다. 이 작업은 적절한 시간에 최소 CPU 시간으로 수행 할 수 있으며 많은 경우 한 줄의 코드 만 필요합니다.

int& int::operator++() { 
     return *this += 1;
}

그러나 같은 것은 말할 수 없다 i++.

후행 증가 i++는 종종 증분 하기 전에 원래 값을 반환하는 것으로 간주됩니다 . 그러나 함수는 완료된 경우에만 결과를 반환 할 수 있습니다 . 결과적으로 원래 값을 포함하는 변수의 사본을 작성하고 변수를 증가시킨 다음 원래 값을 보유한 사본을 리턴해야합니다.

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

사전 증분과 사후 증분간에 기능적 차이가없는 경우 컴파일러는 둘 사이의 성능 차이가 없도록 최적화를 수행 할 수 있습니다. 복합 데이터가 같은 입력 한 경우, struct또는 class관여, 복사 생성자는 후행 증가에 호출됩니다, 깊은 사본이 필요한 경우이 최적화를 수행 할 수 없습니다. 따라서 사전 증분은 일반적으로 사후 증분보다 빠르며 더 적은 메모리를 필요로합니다.


1

@ 마크 : 이전 답변이 약간 뒤집 혔기 때문에 삭제했으며 그 자체만으로도 가치가 떨어졌습니다. 실제로 많은 사람들의 마음에 무엇이 있는지 묻는다는 점에서 좋은 질문이라고 생각합니다.

일반적인 대답은 ++ i가 i ++보다 빠르다는 것입니다. 의심 할 여지없이 더 큰 질문은 "언제주의해야합니까?"입니다.

반복자를 증가시키는 데 소비 된 CPU 시간의 비율이 10 % 미만이면 신경 쓰지 않아도됩니다.

반복자를 증가시키는 데 소비 된 CPU 시간의 비율이 10 %를 초과하면 반복을 수행하는 명령문을 확인할 수 있습니다. 반복자를 사용하지 않고 정수만 증가시킬 수 있는지 확인하십시오. 가능성은 있지만, 어떤 의미에서는 바람직하지는 않지만, 이터레이터에서 소비하는 모든 시간을 절약 할 수있는 기회는 꽤 좋습니다.

반복자 증가가 시간의 90 % 이상을 소비하는 예를 보았습니다. 이 경우 정수를 늘리면 실행 시간이 기본적으로 그 양만큼 줄어 듭니다. (즉, 10 배 속도 향상)


1

@wilhelmtell

컴파일러는 임시를 제거 할 수 있습니다. 다른 스레드에서 그대로 :

C ++ 컴파일러는 스택 기반 임시를 제거하여 프로그램 동작이 변경 되더라도이를 제거 할 수 있습니다. VC 8 용 MSDN 링크 :

http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx


1
관련이 없습니다. NRVO는 "CC :: operator ++ (int)"의 t를 호출자에게 다시 복사 할 필요가 없지만, i ++는 여전히 호출자의 스택에 이전 값을 복사합니다. NRVO가 없으면 i ++는 2 개의 사본을 작성합니다 (하나에서 t로, 하나는 호출자에게 다시).
Blaisorblade

0

성능상의 이점이없는 내장 유형에서도 ++ i를 사용해야하는 이유는 좋은 습관을 만드는 것입니다.


3
미안하지만 저를 귀찮게합니다. 거의 중요하지 않을 때 누가 "좋은 습관"이라고 말합니까? 사람들이 그것을 규율의 일부로 만들고 싶다면 괜찮지 만 개인 취향의 문제와 중요한 이유를 구별합시다.
Mike Dunlavey

@MikeDunlavey 좋아, 상관없이 일반적으로 어느 쪽을 사용합니까? xD 하나 또는 다른 하나가 아닙니다! 포스트 ++ (일반적인 의미로 사용하는 경우 업데이트하십시오. 이전을 반환하십시오)는 ++ pre (업데이트, 반환)보다 완전히 열등합니다. 성능이 떨어지고 싶지 않은 이유는 없습니다. 나중에 업데이트하고 싶을 경우 프로그래머는 포스트 ++을 전혀하지 않습니다. 이미 가지고있을 때 시간을 낭비하지 않아도됩니다. 사용 후 업데이트하십시오. 그런 다음 컴파일러는 원하는 상식을 갖습니다.
Puddle

@Puddle :이 말을들을 때 : "성능이 떨어지고 싶은 이유는 없습니다."나는 "페니 현명한-파운드 어리 석음"이라는 말을 듣고 있습니다. 관련된 규모에 대한 이해가 필요합니다. 이것이 관련된 시간의 1 % 이상을 차지하는 경우에만 생각을해야합니다. 일반적으로 이것에 대해 생각하고 있다면, 고려 하지 않은 백만 배 더 큰 문제가 있으며, 이것이 소프트웨어를 훨씬 더 느리게 만드는 것입니다.
마이크 던 라비

@MikeDunlavey는 자존심을 만족시키기 위해 넌센스를 역류했습니다. 당신은 모든 현명한 승려처럼 들리려고 노력하고 있지만 아무 말도하지 않습니다. 관련된 크기 ... 시간의 1 % 이상 만 신경 써야 할 경우 ... xD 절대 드리블. 비효율적이라면, 알고 고칠 가치가 있습니다. 우리는이 정확한 이유 때문에 이것을 숙고하고 있습니다! 우리는이 지식으로부터 얼마나 많이 얻을 수 있을지 걱정하지 않습니다. 성능 저하를 원치 않는다고 말했을 때 다음 시나리오를 설명하십시오. 미스터 와이즈!
Puddle

0

둘 다 빠릅니다.) 프로세서에 대해 동일한 계산이 필요한 경우, 수행 순서가 다릅니다.

예를 들어, 다음 코드는

#include <stdio.h>

int main()
{
    int a = 0;
    a++;
    int b = 0;
    ++b;
    return 0;
}

다음 어셈블리를 생성하십시오.

 0x0000000100000f24 <main+0>: push   %rbp
 0x0000000100000f25 <main+1>: mov    %rsp,%rbp
 0x0000000100000f28 <main+4>: movl   $0x0,-0x4(%rbp)
 0x0000000100000f2f <main+11>:    incl   -0x4(%rbp)
 0x0000000100000f32 <main+14>:    movl   $0x0,-0x8(%rbp)
 0x0000000100000f39 <main+21>:    incl   -0x8(%rbp)
 0x0000000100000f3c <main+24>:    mov    $0x0,%eax
 0x0000000100000f41 <main+29>:    leaveq 
 0x0000000100000f42 <main+30>:    retq

a ++ 및 b ++의 경우 니모닉을 포함하므로 동일한 작업입니다.)


OP는 C ++을 요구했지만 C입니다. C에서도 마찬가지입니다. C ++에서는 ++ i가 빠릅니다. 개체로 인해. 그러나 일부 컴파일러는 증분 후 연산자를 최적화 할 수 있습니다.
Wiggler Jtag

0

의도 된 질문은 결과가 사용되지 않은 시점에 관한 것입니다 (C의 질문에서 명확합니다). 질문이 "커뮤니티 위키"이므로 누군가이 문제를 해결할 수 있습니까?

조기 최적화에 대해서는 Knuth가 종종 인용됩니다. 맞습니다. 그러나 Donald Knuth는 요즘 볼 수있는 끔찍한 코드로 결코 방어하지 않을 것입니다. Java 정수 중 a = b + c를 본 적이 있습니까? 이는 3 개의 권투 / unboxing 전환에 해당합니다. 그런 것들을 피하는 것이 중요합니다. 그리고 ++ i 대신 쓸데없이 i ++를 쓰는 것은 같은 실수입니다. 편집 : phresnel이 주석에 멋지게 언급했듯이 이것은 "조기 비관 화와 마찬가지로 조기 최적화는 악의입니다"라고 요약 할 수 있습니다.

사람들이 i ++에 더 익숙하다는 사실조차도 K & R의 개념적 실수로 인한 불행한 C 유산입니다 (의도적 주장을 따르면 논리적 인 결론입니다 .K & R이 의미가 없기 때문에 K & R을 지키는 것은 무의미합니다. 훌륭하지만 언어 디자이너만큼 훌륭하지는 않습니다 .C 디자인에는 gets ()에서 strcpy ()까지, strncpy () API에 이르기까지 수많은 실수가 있습니다 (1 일 이후 strlcpy () API를 가져야했습니다) ).

Btw, 나는 ++ i를 읽는 데 성가신 것을 찾기 위해 C ++에 충분히 익숙하지 않은 사람들 중 하나입니다. 아직도, 나는 그것이 옳다는 것을 인정하기 때문에 그것을 사용합니다.


박사 과정을 밟고 계신 것 같습니다. 컴파일러 최적화와 그런 것들에 관심이 있습니다. : 그 위대한, 그러나 학계를 잊지 않는 것은 에코 챔버이며, 당신이에 관심이있을 수있는 CS에서 적어도 상식이 자주 문 밖에 남아됩니다 stackoverflow.com/questions/1303899/...
마이크 Dunlavey

나는 ( ++ii++사실은 더 시원하다는 것보다) 성가신 것을 발견하지 못했지만 나머지 게시물은 내 인정을 얻습니다. 어쩌면 "조기 비관 화와 마찬가지로 조기 최적화는 나쁘다"는 말을 추가하십시오.
Sebastian Mach

strncpy당시에 사용하던 파일 시스템에서 목적을 달성했습니다. 파일 이름은 8 자 버퍼이며 null로 종료하지 않아도됩니다. 언어 진화의 미래에 40 년을 보지 못했다고 비난 할 수는 없습니다.
MM

@MattMcNabb : MS-DOS 독점 파일 이름이 8자가 아니 었습니까? C는 유닉스와 함께 발명되었습니다. 어쨌든 strncpy에 요점이 있더라도 strlcpy의 부족은 완전히 정당화되지 않았습니다. 원래의 C조차도 오버플로해서는 안되는 배열이 있었고 strlcpy가 필요했습니다. 기껏해야 그들은 버그를 악용하려는 공격자 만 빠져있었습니다. 그러나이 문제를 예측하는 것이 사소한 것이라고 말할 수 없으므로 게시물을 다시 작성하면 같은 톤을 사용하지 않습니다.
Blaisorblade

@Blaisorblade : 제가 기억 하듯이 초기 UNIX 파일 이름은 14 자로 제한되었습니다. 의 부족은 strlcpy()그것이 아직 발명되지 않았다는 사실에 의해 정당화되었다.
Keith Thompson

-1

사람들에게 지혜의 보석을 제공 할 시간;)-C ++ 접미사 증가를 접두사 증가와 거의 동일하게 만드는 간단한 트릭이 있습니다 (나 자신을 위해 발명했지만 다른 사람들의 코드에서도 보았습니다. 혼자).

기본적으로 트릭은 도우미 클래스를 사용하여 반환 후 증가를 연기하고 RAII가 구조합니다.

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

일부 커스텀 반복자 코드를 위해 개발되었으며 런타임을 줄입니다. 접두사 대 접미사의 비용은 이제 하나의 참조이며, 이것이 많이 움직이고있는 맞춤형 연산자 인 경우 접두사와 접두사가 동일한 런타임을 얻었습니다.


-5

++ii++값의 오래된 사본을 반환하지 않기 때문에 보다 빠릅니다 .

또한 더 직관적입니다.

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

이 C 예제 는 "12"대신 "02" 인쇄합니다.

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

C ++동일 :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.