루프 내부에 변수 선언, 좋은 연습 또는 나쁜 연습? 사용할 위치에 가깝게

질문 # 1 : 루프 안에서 변수를 선언하는 것이 좋은 습관입니까, 나쁜 습관입니까?

성능 문제가 있는지 여부에 대한 다른 스레드를 읽었으며 (거의 아니요), 항상 변수를 사용할 위치에 가깝게 선언해야합니다. 내가 궁금해하는 것은 이것을 피해야하는지 아닌지 또는 실제로 선호되는지입니다.

예:

for(int counter = 0; counter <= 10; counter++)
{
   string someString = "testing";

   cout << someString;
}

질문 # 2 : 대부분의 컴파일러는 변수가 이미 선언되었다는 것을 인식하고 그 부분을 건너 뛰거나 실제로 매번 메모리에 변수를 지정합니까?



답변

이것은 훌륭한 연습입니다.

루프 내부에 변수를 작성하면 해당 범위가 루프 내부로 제한됩니다. 루프 외부에서 참조하거나 호출 할 수 없습니다.

이 방법:

  • 변수의 이름이 “generic”과 같은 비트 (예 : “i”)이면 나중에 코드에서 같은 이름의 다른 변수와 혼합 할 위험이 없습니다 ( -WshadowGCC 의 경고 명령어를 사용하여 완화 할 수도 있음 ).

  • 컴파일러는 변수 범위가 루프 내부로 제한되므로 변수가 실수로 다른 곳에서 참조되는 경우 적절한 오류 메시지를 발행합니다.

  • 마지막으로, 일부 전용 최적화는 변수가 루프 외부에서 사용될 수 없다는 것을 알고 있기 때문에 컴파일러 (가장 중요한 레지스터 할당)에 의해보다 효율적으로 수행 될 수 있습니다. 예를 들어, 나중에 재사용하기 위해 결과를 저장할 필요가 없습니다.

요컨대, 당신은 그것을 할 권리가 있습니다.

그러나 변수는 각 루프 사이에 값을 유지하지 않아야합니다 . 이 경우 매번 초기화해야 할 수도 있습니다. 루프를 포괄하는 더 큰 블록을 만들 수도 있는데, 그 목적은 한 루프에서 다른 루프로 값을 유지해야하는 변수를 선언하는 것입니다. 여기에는 일반적으로 루프 카운터 자체가 포함됩니다.

{
    int i, retainValue;
    for (i=0; i<N; i++)
    {
       int tmpValue;
       /* tmpValue is uninitialized */
       /* retainValue still has its previous value from previous loop */

       /* Do some stuff here */
    }
    /* Here, retainValue is still valid; tmpValue no longer */
}

질문 # 2의 경우 : 함수가 호출 될 때 변수가 한 번 할당됩니다. 실제로 할당 관점에서 보면 함수 시작 부분에서 변수를 선언하는 것과 거의 같습니다. 유일한 차이점은 범위입니다. 변수는 루프 외부에서 사용할 수 없습니다. 변수가 할당되지 않았을 수도 있습니다. 사용 가능한 슬롯 (범위가 종료 된 다른 변수에서)을 다시 사용하기 만하면됩니다.

제한적이고 정확한 범위로보다 정확한 최적화가 이루어집니다. 그러나 더 중요한 것은 코드의 다른 부분을 읽을 때 걱정할 필요가 적은 상태 (예 : 변수)로 코드를 더 안전하게 만듭니다.

이것은 if(){...}블록 외부에서도 마찬가지 입니다. 일반적으로 대신 :

    int result;
    (...)
    result = f1();
    if (result) then { (...) }
    (...)
    result = f2();
    if (result) then { (...) }

작성하는 것이 더 안전합니다.

    (...)
    {
        int const result = f1();
        if (result) then { (...) }
    }
    (...)
    {
        int const result = f2();
        if (result) then { (...) }
    }

특히 작은 예에서는 차이가 작게 보일 수 있습니다. 그러나 더 큰 코드 기반에서는 도움이 될 것입니다. 이제 일부 resultf1()f2()블록 으로 전송할 위험이 없습니다 . 각각 result은 자체 범위로 엄격히 제한되어 역할이 더욱 정확합니다. 리뷰어의 관점에서 보면 걱정하고 추적해야 할 장거리 상태 변수 가 적기 때문에 훨씬 좋습니다.

컴파일러가 더 나은 도움을 줄 것입니다. 미래에 약간의 잘못된 코드 변경 후으로 result제대로 초기화되지 않았다고 가정합니다 f2(). 두 번째 버전은 컴파일 타임에 명확한 오류 메시지 (런타임보다 낫다)를 나타내는 작동을 거부합니다. 첫 번째 버전은 아무 것도 발견하지 못합니다. 그 결과 f1()는 단순히 두 번째로 테스트되어 결과에 대해 혼란스러워 f2()집니다.

보완 정보

오픈 소스 도구 인 CppCheck (C / C ++ 코드를위한 정적 분석 도구)는 최적의 변수 범위에 관한 훌륭한 힌트를 제공합니다.

할당에 대한 의견에 대한 답변 : 위의 규칙은 C에서는 true이지만 일부 C ++ 클래스에는 해당되지 않을 수 있습니다.

표준 유형 및 구조의 경우 변수 크기는 컴파일 타임에 알려져 있습니다. C에는 “구성”과 같은 것이 없으므로 함수를 호출 할 때 변수를위한 공간이 스택에 할당됩니다 (초기화없이). 이것이 루프 내부에서 변수를 선언 할 때 “제로”비용이 발생하는 이유입니다.

그러나 C ++ 클래스의 경우이 생성자 항목이 훨씬 적습니다. 컴파일러는 동일한 공간을 재사용하기에 충분히 영리해야하기 때문에 할당이 문제가되지 않을 것이라고 생각하지만 초기화는 각 루프 반복에서 발생할 수 있습니다.


답변

일반적으로 매우 가깝게 유지하는 것이 좋습니다.

경우에 따라 루프에서 변수를 꺼내는 것을 정당화하는 성능과 같은 고려 사항이 있습니다.

귀하의 예에서, 프로그램은 매번 문자열을 작성하고 파기합니다. 일부 라이브러리는 작은 문자열 최적화 (SSO)를 사용하므로 경우에 따라 동적 할당을 피할 수 있습니다.

중복 생성 / 할당을 피하려고한다고 가정하면 다음과 같이 작성합니다.

for (int counter = 0; counter <= 10; counter++) {
   // compiler can pull this out
   const char testing[] = "testing";
   cout << testing;
}

또는 상수를 꺼낼 수 있습니다.

const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
   cout << testing;
}

대부분의 컴파일러는 변수가 이미 선언되었으며 해당 부분을 건너 뛰거나 실제로 매번 메모리에 스팟을 생성합니까?

변수가 소비 하는 공간을 재사용 할 수 있으며 루프에서 불변을 가져올 수 있습니다. const char 배열 (위)의 경우 해당 배열을 가져올 수 있습니다. 그러나 생성자와 소멸자는 객체 (예 :와 같은 std::string) 의 각 반복에서 실행되어야합니다 . 의 경우 std::string해당 ‘space’에는 문자를 나타내는 동적 할당이 포함 된 포인터가 포함됩니다. 그래서 이거:

for (int counter = 0; counter <= 10; counter++) {
   string testing = "testing";
   cout << testing;
}

각 경우에 중복 복사가 필요하고 변수가 SSO 문자 수에 대한 임계 값보다 높은 경우 동적 할당 및 해제가 필요합니다 (그리고 SSO는 std 라이브러리에 의해 구현 됨).

이것을하는 것 :

string testing;
for (int counter = 0; counter <= 10; counter++) {
   testing = "testing";
   cout << testing;
}

각 반복에서 문자의 실제 사본이 여전히 필요하지만 문자열을 할당하기 때문에 양식에 하나의 동적 할당이 발생할 수 있으며 구현시 문자열의 백업 할당 크기를 조정할 필요가 없어야합니다. 물론,이 예제에서는 그렇게하지 않을 것입니다 (여러 개의 대체 대안이 이미 입증 되었기 때문에). 문자열이나 벡터의 내용이 다를 때 고려할 수도 있습니다.

그렇다면 모든 옵션 (및 그 이상)으로 무엇을하십니까? 비용을 잘 이해하고 언제 이탈해야 할지를 알 때까지 기본값을 매우 가깝게 유지하십시오.


답변

C ++의 경우 수행중인 작업에 따라 다릅니다. 좋아, 바보 같은 코드지만 상상 해봐

class myTimeEatingClass
{
 public:
 //constructor
      myTimeEatingClass()
      {
          sleep(2000);
          ms_usedTime+=2;
      }
      ~myTimeEatingClass()
      {
          sleep(3000);
          ms_usedTime+=3;
      }
      const unsigned int getTime() const
      {
          return  ms_usedTime;
      }
      static unsigned int ms_usedTime;
};
myTimeEatingClass::ms_CreationTime=0;
myFunc()
{
    for (int counter = 0; counter <= 10; counter++) {

        myTimeEatingClass timeEater();
        //do something
    }
    cout << "Creating class took "<< timeEater.getTime() <<"seconds at all<<endl;

}
myOtherFunc()
{
    myTimeEatingClass timeEater();
    for (int counter = 0; counter <= 10; counter++) {
        //do something
    }
    cout << "Creating class took "<< timeEater.getTime() <<"seconds at all<<endl;

}

myFunc의 출력을 얻을 때까지 55 초 동안 기다립니다. 각 루프 생성자와 소멸자가 함께 완료하는 데 5 초가 걸리기 때문입니다.

myOtherFunc의 출력을 얻을 때까지 5 초가 필요합니다.

물론 이것은 미친 예입니다.

그러나 생성자 및 / 또는 소멸자가 약간의 시간이 필요할 때 각 루프가 동일한 구성을 수행 할 때 성능 문제가 될 수 있음을 보여줍니다.


답변

나는 JeremyRR의 질문에 답하기 위해 글을 올리지 않았다. 대신, 나는 단지 제안을하기 위해 게시했다.

JeremyRR에게 다음을 수행 할 수 있습니다.

{
  string someString = "testing";

  for(int counter = 0; counter <= 10; counter++)
  {
    cout << someString;
  }

  // The variable is in scope.
}

// The variable is no longer in scope.

나는 (프로그래밍을 처음 시작할 때 몰랐다.) “괄호 (쌍으로 묶인 한)”가 “if”, “for”, “다음에 오는 것이 아니라 코드의 어느 곳에 나 위치 할 수 있다는 것을 알고 있는지 모른다. while “등

내 코드는 Microsoft Visual C ++ 2010 Express에서 컴파일되었으므로 작동한다는 것을 알고 있습니다. 또한 변수가 정의 된 대괄호 외부에서 변수를 사용하려고 시도하고 오류가 발생하여 변수가 “파괴”되었음을 알았습니다.

레이블이없는 많은 괄호가 코드를 빠르게 읽을 수 없게 만들 수 있지만 일부 주석은 문제를 해결할 수 있으므로이 방법을 사용하는 것이 나쁜 습관인지 잘 모르겠습니다.


답변

위의 모든 대답이 질문의 이론적 측면을 제공하므로 코드를 엿볼 수 있습니다 .GEEKSFORGEEKS보다 DFS를 해결하려고했습니다. 최적화 문제가 발생했습니다 … 루프 외부의 정수를 선언하는 코드를 해결하면 최적화 오류가 발생합니다.

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
int flag=0;
int top=0;
while(!st.empty()){
    top = st.top();
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

이제 루프 안에 정수를 넣으면 정답을 얻을 수 있습니다 …

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
// int flag=0;
// int top=0;
while(!st.empty()){
    int top = st.top();
    int flag = 0;
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

이것은 @justin 선생님이 두 번째 의견에서 말한 것을 완전히 반영합니다 ….
https://practice.geeksforgeeks.org/problems/depth-first-traversal-for-a-graph/1 . 그냥 쏴라 …. 알겠다.이 도움을 바란다.


답변

4.8 장 K & R의 C 프로그래밍 언어 에서의 블록 구조 2.Ed. :

블록에 선언되고 초기화 된 자동 변수는 블록을 입력 할 때마다 초기화됩니다.

나는 책에서 다음과 같은 관련 설명을 보지 못했을 수도 있습니다.

블록에서 선언되고 초기화 된 자동 변수는 블록이 입력되기 전에 한 번만 할당됩니다.

그러나 간단한 테스트를 통해 가정을 증명할 수 있습니다.

 #include <stdio.h>

 int main(int argc, char *argv[]) {
     for (int i = 0; i < 2; i++) {
         for (int j = 0; j < 2; j++) {
             int k;
             printf("%p\n", &k);
         }
     }
     return 0;
 }