루프 조건에서 사용하면 strlen이 여러 번 계산됩니까?


109

다음 코드가 중복 계산을 유발할 수 있는지 아니면 컴파일러에 특정한 것인지 잘 모르겠습니다.

for (int i = 0; i < strlen(ss); ++i)
{
    // blabla
}

strlen()때마다 시간을 계산할 수 i증가?


14
나는 'ss'가 루프에서 절대 변하지 않는다는 것을 감지 할 수있는 정교한 최적화 없이는 그렇다고 추측 할 것입니다. 컴파일하는 것이 가장 좋으며 어셈블리를 살펴보십시오.
MerickOWA

6
컴파일러, 최적화 수준 및 ss루프 내부에서 수행 할 수있는 작업에 따라 다릅니다 .
Hristo Iliev 2012

4
컴파일러가 ss그것이 결코 수정되지 않았다는 것을 증명할 수 있다면, 루프 밖으로 계산을 끌어 올릴 수 있습니다.
Daniel Fischer

10
@Mike : "strlen이하는 일에 대한 컴파일 타임 분석이 필요합니다."-strlen은 아마도 내장 함수일 것입니다.이 경우 최적화 프로그램은 그것이 무엇을하는지 알고 있습니다.
Steve Jessop

3
@MikeSeymour : 아마 없을 수도 있습니다. strlen은 C 언어 표준에 의해 정의되고 그 이름은 언어에 의해 정의 된 사용을 위해 예약되어 있으므로 프로그램은 다른 정의를 자유롭게 제공 할 수 없습니다. 컴파일러와 옵티마이 저는 strlen이 입력에 전적으로 의존하고 입력이나 전역 상태를 수정하지 않는다고 가정 할 수 있습니다. 여기서 최적화에 대한 도전은 ss가 가리키는 메모리가 루프 내부의 코드에 의해 변경되지 않는다는 것을 결정하는 것입니다. 이는 특정 코드에 따라 현재 컴파일러로 완전히 가능합니다.
Eric Postpischil

답변:


138

예, strlen()각 반복에서 평가됩니다. 이상적인 상황에서 옵티마이 저는 가치가 변하지 않을 것이라고 추론 할 수 있지만 개인적으로는 그것에 의존하지 않을 것입니다.

나는 뭔가를 할 것이다

for (int i = 0, n = strlen(ss); i < n; ++i)

또는 아마도

for (int i = 0; ss[i]; ++i)

반복하는 동안 문자열이 길이를 변경하지 않는 한. 가능하다면 strlen()매번 호출 하거나 더 복잡한 논리를 통해 처리해야합니다.


14
문자열을 조작하고 있지 않다는 것을 안다면 두 번째는 본질적으로 strlen어쨌든 수행되는 루프이기 때문에 훨씬 더 바람직 합니다.
mlibby

26
@alk : 문자열이 짧아지면 둘 다 잘못된 것입니다.
Mike Seymour 2012

3
@alk : 문자열을 변경하는 경우 for 루프는 각 문자를 반복하는 가장 좋은 방법이 아닐 수 있습니다. while 루프가 인덱스 카운터를 관리하기 더 직접적이고 더 쉽다고 생각합니다.
mlibby

2
이상적인 상황 에는 Linux에서 GCC로 컴파일하는 것이 포함 strlen되며 __attribute__((pure)), 컴파일러가 여러 호출을 제거 할 수 있도록 표시됩니다 . GCC는 속성
데이비드 로드리게스 - dribeas

6
두 번째 버전은 이상적이고 가장 관용적 인 형식입니다. 두 번이 아닌 한 번만 문자열을 전달할 수 있으므로 긴 문자열에 대해 훨씬 더 나은 성능 (특히 캐시 일관성)이 있습니다.
R .. GitHub STOP HELPING ICE

14

예, 루프를 사용할 때마다. 그런 다음 매번 문자열의 길이를 계산합니다. 그래서 다음과 같이 사용하십시오.

char str[30];
for ( int i = 0; str[i] != '\0'; i++)
{
//Something;
}

위의 코드 에서 루프가주기를 시작할 때마다 str[i]위치에서 문자열의 특정 문자 하나만 확인 i하므로 메모리가 덜 사용되며 더 효율적입니다.

자세한 내용은이 링크 를 참조하십시오.

아래 코드에서 루프가 실행될 때마다 strlen전체 문자열의 길이를 계산하여 효율성이 떨어지고 시간과 메모리를 더 많이 사용합니다.

char str[];
for ( int i = 0; i < strlen(str); i++)
{
//Something;
}

3
"[it] is more 효율적"에 동의 할 수 있지만 메모리를 적게 사용합니까? 내가 생각할 수있는 유일한 메모리 사용량 차이는 호출하는 동안 호출 스택에 strlen있을 것입니다. 그렇게 빡빡하게 실행한다면 다른 함수 호출도 제거하는 것에 대해 생각해야합니다.
CVn

@ MichaelKjörling "strlen"을 사용하면 루프에서 루프가 실행될 때마다 전체 문자열을 스캔해야하는 반면 위의 코드에서 "str [ix]"는 각주기 동안 하나의 요소 만 스캔합니다. 위치가 "ix"로 표시되는 루프. 따라서 "strlen"보다 적은 메모리를 사용합니다.
codeDEXTER

1
사실 그게 말이되는지 잘 모르겠습니다. 매우 순진한 strlen 구현은 int strlen(char *s) { int len = 0; while(s[len] != '\0') len++; return len; }답변의 코드에서 수행하는 작업과 거의 같습니다. 나는 문자열을 두 번이 아닌 한 번 반복하는 것이 더 시간 효율적 이라고 주장하는 것이 아니지만 어느 쪽이든 더 많거나 적은 메모리를 사용하는 것을 보지 못합니다. 아니면 문자열 길이를 유지하는 데 사용되는 변수를 언급하고 있습니까?
a CVn

@ MichaelKjörling 위의 편집 된 코드와 링크를 참조하십시오. 그리고 메모리에 관해서는 루프가 실행될 때마다 반복되는 모든 값이 메모리에 저장되며 'strlen'의 경우 전체 문자열을 반복해서 계산하기 때문에 더 많은 메모리가 필요합니다. 또한 Java와 달리 C ++에는 "Garbage Collector"가 없기 때문입니다. 그러면 나도 틀릴 수 있습니다. C ++에서 "Garbage Collector"의 부재에 관한 링크 를 참조하십시오 .
codeDEXTER

1
@ aashis2s 가비지 수집기가 없다는 것은 힙에 개체를 만들 때만 역할을합니다. 스택의 개체는 범위가 끝나는 즉시 파괴됩니다.
Ikke

9

좋은 컴파일러는 매번 그것을 계산하지 않을 수 있지만 모든 컴파일러가 그것을 수행한다는 것을 확신 할 수는 없다고 생각합니다.

그 외에도 컴파일러는 strlen(ss)변경되지 않는 것을 알아야 합니다. 이것은 루프 ss에서 변경되지 않은 경우에만 해당됩니다 for.

예를 들어 ssin for루프 에서 읽기 전용 함수를 사용 하지만 ss-parameter를 다음과 같이 선언하지 않는 경우const 컴파일러는 ss루프에서 변경되지 않았 strlen(ss)으며 모든 반복에서 계산 해야한다는 것을 알 수 없습니다 .


3
+1 : 루프 ss에서 변경 해서는 안됩니다 for. 루프에서 호출 된 함수에서 액세스 할 수없고 변경할 수 없어야합니다 (인수로 전달되거나 전역 변수 또는 파일 범위 변수이기 때문에). Const-qualification도 요인이 될 수 있습니다.
Jonathan Leffler

4
나는 컴파일러가 'ss'가 변하지 않는다는 것을 알 가능성이 거의 없다고 생각합니다. 길잃은 포인터가있을 수있는 컴파일러는 'SS'를 변경할 수있는의 아무 생각이 없습니다 메모리 내부의 'SS'를 가리킨
MerickOWA

Jonathan이 맞습니다. 로컬 const 문자열은 컴파일러가 'ss'를 변경할 수있는 방법이 없음을 확인하는 유일한 방법 일 수 있습니다.
MerickOWA

2
@MerickOWA : 실제로 restrictC99에서 사용 하는 것 중 하나입니다 .
스티브 제솝

4
마지막 파라에 대해서 : 당신이 읽기 전용 함수를 호출하는 경우 ss에 대한 루프에서, 다음의 매개 변수가 선언 된 경우에도 const char*, 컴파일러는 여전히 (a)는이 것을 알고 중 하나를 제외하고 길이를 다시 계산해야 ssCONST 객체에 포인트를, 상수에 대한 포인터가되는 것과는 반대로 또는 (b) 함수를 인라인하거나 그렇지 않으면 읽기 전용임을 확인할 수 있습니다. 복용하는 const char*매개 변수 것은 하지 가로 캐스트에 유효하기 때문에, 데이터는 지적 수정할 수없는 약속 char*및 수정 객체가 const를하지 않고 문자열 리터럴 아님을 제공 수정합니다.
Steve Jessop

4

경우 ss유형 인 const char *당신은 멀리 캐스팅하지 않는 const컴파일러는 호출 할 수있는 루프 내 다움을 strlen최적화가 켜져있는 경우, 한 번. 그러나 이것은 확실히 믿을 수있는 행동이 아닙니다.

strlen결과를 변수에 저장 하고이 변수를 루프에서 사용해야합니다. 추가 변수를 생성하고 싶지 않다면 수행중인 작업에 따라 루프를 역방향으로 되 돌리는 데 도움이 될 수 있습니다.

for( auto i = strlen(s); i > 0; --i ) {
  // do whatever
  // remember value of s[strlen(s)] is the terminating NULL character
}

1
전화하는 것은 실수 strlen입니다. 끝까지 반복하면됩니다.
R .. GitHub STOP HELPING ICE

i > 0? i >= 0여기 있으면 안 되나요? 개인적으로 strlen(s) - 1문자열을 거꾸로 반복하는 경우 부터 시작 하고 종료 \0는 특별한 고려 가 필요하지 않습니다.
a CVn

2
@ MichaelKjörling은로 i >= 0초기화하는 경우에만 작동 strlen(s) - 1하지만 길이가 0 인 문자열이 있으면 초기 값이 언더 플로우됩니다.
Praetorian

@ Prætorian, 길이가 0 인 문자열에 대한 좋은 지적입니다. 나는 내 의견을 쓸 때 그 경우를 고려하지 않았습니다. C ++는 i > 0초기 루프 항목 에서 표현식을 평가합니까 ? 그렇지 않다면, 당신이 맞습니다. 길이가 0 인 경우는 확실히 루프를 깨뜨릴 것입니다. 만약 그렇다면, 당신은 "간단하게"부호있는 i== -1 <0을 얻으 므로 조건이이면 루프 항목이 없습니다 i >= 0.
a CVn

@ MichaelKjörling 예, 처음으로 루프를 실행하기 전에 종료 조건이 평가됩니다. strlen의 반환 유형은 부호가 없으므로 (strlen(s)-1) >= 0길이가 0 인 문자열에 대해 true로 평가됩니다.
Praetorian

3

공식적으로 예, strlen()모든 반복에 대해 호출 될 것으로 예상됩니다.

어쨌든 나는 첫 번째 이후 strlen ()에 대한 연속적인 호출을 최적화하는 영리한 컴파일러 최적화의 존재 가능성을 부정하고 싶지 않습니다.


3

그 전체의 술어 코드는 for루프의 모든 반복에서 실행됩니다 . strlen(ss)호출 결과를 기억하기 위해 컴파일러는 최소한

  1. 기능 strlen은 부작용이 없었습니다.
  2. 가 가리키는 메모리 ss는 루프 기간 동안 변경되지 않습니다.

컴파일러는 이러한 것들을 알지 못하기 때문에 첫 번째 호출의 결과를 안전하게 기억할 수 없습니다.


그런데 그것은 정적 분석과 함께 그 일을 알고,하지만 난 당신의 지점이 같은 분석은 현재 네, 컴파일러 ++ 어떤 C에서 구현되지 않는 것입니다 생각하십니까?
GManNickG

@GManNickG 확실히 # 1을 증명할 수 있지만 # 2는 더 어렵습니다. 단일 스레드의 경우 확실히 증명할 수 있지만 다중 스레드 환경에서는 그렇지 않습니다.
JaredPar

1
아마도 저는 고집 스럽지만 멀티 스레드 환경에서도 두 번째가 가능하다고 생각하지만 강력한 추론 시스템 없이는 확실히 불가능합니다. 그래도 여기서 생각하고 있습니다. 확실히 현재 C ++ 컴파일러의 범위를 벗어납니다.
GManNickG

@GManNickG 나는 그것이 C / C ++에서 가능하다고 생각하지 않습니다. 의 주소 ss를 a로 매우 쉽게 숨기 size_t거나 여러 byte값으로 나눌 수 있습니다. 내 사악한 스레드는 해당 주소에 바이트를 쓸 수 있으며 컴파일러는 ss.
JaredPar

1
@JaredPar : 죄송합니다. int a = 0; do_something(); printf("%d",a);최적화 할 수없는 do_something()것은 초기화되지 않은 int 작업을 수행 할 수 있거나 스택을 백업하여 a의도적으로 수정할 수 있다고 주장 할 수 있습니다 . 사실의 점에서, GCC 4.5에 최적화를 수행 do_something(); printf("%d",0);-O3와
스티브 Jessop

2

. strlen은 i가 증가 할 때마다 계산됩니다.

당신이 경우 SS를 변경하지 않은 으로 루프에서 그 의미 논리에 영향을 미치지 않습니다 그렇지 않으면 영향을 미칠 것입니다.

다음 코드를 사용하는 것이 더 안전합니다.

int length = strlen(ss);

for ( int i = 0; i < length ; ++ i )
{
 // blabla
}

2

예,는 strlen(ss)각 반복에서 길이를 계산합니다. 당신이 ss어떤 식 으로든 증가하고 또한 증가하는 경우 i; 무한 루프가 있습니다.


2

예, 루프가 평가 될 때마다strlen() 함수가 호출 됩니다 .

효율성을 높이고 싶다면 항상 모든 것을 지역 변수에 저장하는 것을 잊지 마십시오. 시간이 걸리지 만 매우 유용합니다 ..

아래와 같은 코드를 사용할 수 있습니다.

String str="ss";
int l = strlen(str);

for ( int i = 0; i < l ; i++ )
{
    // blablabla
}


2

요즘은 흔하지 않지만 20 년 전 16 비트 플랫폼에서는 다음을 권장합니다.

for ( char* p = str; *p; p++ ) { /* ... */ }

컴파일러가 최적화에서 그다지 똑똑하지 않더라도 위의 코드는 아직 좋은 어셈블리 코드를 만들 수 있습니다.


1

예. 테스트는 ss가 루프 내에서 변경되지 않는다는 것을 알지 못합니다. 변경되지 않는다는 것을 알고 있다면 다음과 같이 작성합니다.

int stringLength = strlen (ss); 
for ( int i = 0; i < stringLength; ++ i ) 
{
  // blabla 
} 

1

아아, 이상적인 상황에서도 젠장!

오늘 (2018 년 1 월) 기준, 컴파일하는 경우 gcc 7.3 및 clang 5.0 :

#include <string.h>

void bar(char c);

void foo(const char* __restrict__ ss) 
{
    for (int i = 0; i < strlen(ss); ++i) 
    {
        bar(*ss);
    }
}    

그래서 우리는 :

  • ss 상수 포인터입니다.
  • ss 표시됩니다 __restrict__
  • 루프 본문은 어떤 식 으로든이 가리키는 메모리를 건드릴 수 없습니다 ss(음,를 위반하지 않는 한 __restrict__).

그리고 여전히 두 컴파일러는 실행 strlen() 이 루프의 매 반복 . 놀랄 만한.

이것은 또한 @Praetorian과 @JaredPar의 암시 / 소망스러운 생각이 풀리지 않는다는 것을 의미합니다.


0

예, 간단한 말로. 그리고 전혀 변경이없는 것을 발견하면 최적화 단계로 컴파일러가 원하는 드문 조건에서 작은 아니오가 ss있습니다. 그러나 안전한 상태에서는 '예'라고 생각해야합니다. in multithreaded및 이벤트 주도 프로그램과 같은 상황이 있지만 아니오라고 생각하면 버그가 발생할 수 있습니다. 프로그램 복잡성을 너무 많이 개선하지 않으므로 안전하게 플레이하십시오.


0

예.

strlen()i증가 할 때마다 계산 되고 최적화되지 않습니다.

아래 코드는 컴파일러가 strlen().

for ( int i = 0; i < strlen(ss); ++i )
{
   // Change ss string.
   ss[i] = 'a'; // Compiler should not optimize strlen().
}

특정 수정을 수행하면 ss의 길이가 변경되지 않고 내용 만 변경되므로 (정말, 정말 영리한) 컴파일러는 여전히 strlen.
Darren Cook

0

쉽게 테스트 할 수 있습니다.

char nums[] = "0123456789";
size_t end;
int i;
for( i=0, end=strlen(nums); i<strlen(nums); i++ ) {
    putchar( nums[i] );
    num[--end] = 0;
}

루프 조건은 루프를 다시 시작하기 전에 각 반복 후 평가됩니다.

또한 문자열 길이를 처리하는 데 사용하는 유형에주의하십시오. stdio에서 size_t와 같이 정의 되어야합니다 unsigned int. 비교하고 캐스팅하면 int심각한 취약성 문제가 발생할 수 있습니다.


0

글쎄, 누군가 "영리한"현대 컴파일러에 의해 기본적으로 최적화되어 있다고 말하는 것을 알아 차렸다. 그나저나 최적화없이 결과를보십시오. 나는 시도했다 :
최소 C 코드 :

#include <stdio.h>
#include <string.h>

int main()
{
 char *s="aaaa";

 for (int i=0; i<strlen(s);i++)
  printf ("a");
 return 0;
}

내 컴파일러 : g ++ (Ubuntu / Linaro 4.6.3-1ubuntu5) 4.6.3
어셈블리 코드 생성 명령 : g ++ -S -masm = intel test.cpp

Gotten assembly code at the output:
    ...
    L3:
mov DWORD PTR [esp], 97
call    putchar
add DWORD PTR [esp+40], 1
    .L2:
     THIS LOOP IS HERE
    **<b>mov    ebx, DWORD PTR [esp+40]
mov eax, DWORD PTR [esp+44]
mov DWORD PTR [esp+28], -1
mov edx, eax
mov eax, 0
mov ecx, DWORD PTR [esp+28]
mov edi, edx
repnz scasb</b>**
     AS YOU CAN SEE it's done every time
mov eax, ecx
not eax
sub eax, 1
cmp ebx, eax
setb    al
test    al, al
jne .L3
mov eax, 0
     .....

문자열 주소가 restrict정규화 되지 않는 한 최적화를 시도한 컴파일러를 신뢰하는 것이 싫습니다. 이러한 최적화가 합법적 인 경우도 있지만, 그러한 경우를 확실하게 식별하는 데 필요한 노력 restrict은 합리적인 조치로 인해 거의 확실히 이점을 초과합니다. 그러나 문자열의 주소에 const restrict한정자가 있으면 다른 것을 보지 않고도 최적화를 정당화하기에 충분할 것입니다.
supercat 2017-06-11

0

Prætorian의 답변에 대해 자세히 설명하면 다음을 권장합니다.

for( auto i = strlen(s)-1; i > 0; --i ) {foo(s[i-1];}
  • autostrlen이 반환하는 유형에 대해 신경 쓰고 싶지 않기 때문입니다. C ++ 11 컴파일러 (예 :gcc -std=c++0x 완전히 C ++ 11은 아니지만 자동 유형이 작동 함)가이를 수행합니다.
  • i = strlen(s)당신이 비교하고 싶기 때문에 0(아래 참조)
  • i > 0 0과의 비교는 다른 숫자에 비해 (약간) 더 빠르기 때문입니다.

단점은 i-1문자열 문자에 액세스하기 위해 사용해야한다는 것 입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.