본문으로 이동

제어 흐름

위키백과, 우리 모두의 백과사전.

제어 흐름(영어: control flow 또는 flow of control)은 실행이 한 명령어에서 다음 명령어로 진행되는 방식을 설명한다. 기계어명령형 프로그래밍 프로그래밍 언어와 같은 많은 문맥에서, 명령어가 제어를 다른 지점으로 전달하는 경우를 제외하면 제어는 순차적으로(현재 실행 중인 명령어 직후에 위치한 명령어로) 진행되며, 이 경우 해당 명령어를 제어 흐름 명령어라고 분류한다. 문맥에 따라 명령어 대신 다른 용어가 사용되기도 한다. 예를 들어 기계어에서는 일반적인 용어가 인스트럭션(instruction)이며, 명령형 언어에서는 스테이트먼트(statement, 구문)라는 용어가 일반적이다.

명령형 언어는 제어 흐름을 명시적으로 인코딩하지만, 다른 프로그래밍 패러다임의 언어들은 제어 흐름에 덜 집중한다. 선언형 프로그래밍 언어는 연산 순서를 규정하지 않고 원하는 결과를 지정한다. 함수형 프로그래밍 언어는 일반적으로 제어 흐름 구문이라고 부르지는 않지만, 흐름을 제어하기 위해 언어 구조함수를 모두 사용한다.

중앙 처리 장치인스트럭션 세트에서 제어 흐름 인스트럭션은 종종 프로그램 카운터를 변경하며, 무조건적인 분기(점프라고도 함) 또는 조건부 분기 중 하나다. 대안적인 접근 방식은 분기 대신 조건에 따라 인스트럭션을 활성화하는 술어화(predication)이다.

인터럽트신호와 같은 비동기 제어 흐름 전달은 제어가 중단된 지점으로 돌아가기 전에 일반적인 제어 흐름을 핸들러로 변경한다.

소프트웨어를 공격하는 한 가지 방법은 실행 흐름을 리다이렉션하는 것이다. 스택 카나리, 버퍼 오버플로 보호, 섀도우 스택, vtable 포인터 검증을 포함한 다양한 제어 흐름 무결성 기술이 이러한 공격을 방어하는 데 사용된다.[1][2][3]

구조

[편집]

제어 흐름은 코드 구조와 밀접하게 관련되어 있다. 제어는 언어의 구조와 실행 규칙에 의해 정의된 라인을 따라 흐른다. 이 일반적인 구조 개념은 구조를 블록 조직에 기반한 순차, 선택 및 반복으로 제한하는 구조적 프로그래밍과 혼동해서는 안 된다.

순차

[편집]

순차적 실행은 가장 기본적인 구조다. 모든 코드가 본질적으로 순차적인 것은 아니지만, 명령형 코드는 순차적이다.

레이블

[편집]

레이블소스 코드 내의 위치를 식별한다. 일부 제어 흐름 구문은 제어가 레이블이 지정된 라인으로 점프하도록 레이블을 참조한다. 위치를 표시하는 것 외에 레이블은 다른 효과가 없다.

일부 언어에서는 레이블을 숫자로 제한하며, 이를 때로는 줄 번호라고 부른다. 다만 줄 번호는 레이블이라기보다는 해당 행의 고유한 인덱스를 의미한다. 그럼에도 불구하고, 이러한 숫자 레이블은 순차적이지 않더라도 일반적으로 파일 내에서 위에서 아래로 증가해야 한다. 예를 들어 BASIC에서는 다음과 같다.

10 LET X = 3
20 PRINT X
30 GOTO 10

많은 언어에서 레이블은 영숫자 식별자이며, 보통 행의 시작 부분에 나타나고 바로 뒤에 콜론이 붙는다. 예를 들어, 다음 C 코드는 3행에 success 레이블을 정의하며, 이는 그 뒤에 오는 첫 번째 구문(4행)을 점프 대상 지점으로 식별한다.

void f(bool ok) {
    if (ok) {
        goto success;
    }
    return;
success:
    printf("OK");
}

블록

[편집]

대부분의 언어는 코드 시퀀스를 블록으로 구성하는 기능을 제공한다. 제어문과 함께 사용될 때, 블록의 시작 부분은 점프 대상이 된다. 예를 들어, 중괄호를 사용하여 블록을 구분하는 다음 C 코드에서 done이 거짓이면 제어는 1행에서 4행으로 점프한다.

if (done) {
    printf("All done");
} else {
    printf("Still workin' on it");
}

제어

[편집]

프로그래밍 언어를 위해 많은 제어 명령어가 고안되었다. 이 섹션은 주목할 만한 구조들을 기능별로 정리하여 설명한다.

함수

[편집]

함수는 제어 흐름을 제공하며, 함수가 호출되면 실행은 함수 코드의 시작 부분으로 점프하고, 완료되면 제어는 호출 지점으로 돌아간다. 다음 C 코드에서 제어는 foo() 함수를 호출하기 위해 6행에서 2행으로 점프한다. 그 후, 함수 본문을 완료("Hi" 출력)한 후, 제어는 호출 이후인 7행으로 돌아간다.

void foo() {
    printf("Hi");
}

void bar() {
    foo();
    printf("Done");
}

분기

[편집]

분기 명령어는 실행 지점을 명령어가 포함된 코드 지점에서 명령어가 지정하는 지점으로 이동시킨다.

점프

[편집]

점프 명령어는 코드의 다른 지점으로 무조건 제어를 분기하며, 코드 흐름을 제어하는 가장 기본적인 형태다.

고급 언어에서는 이것이 종종 goto 구문으로 제공된다. 언어에 따라 키워드가 대문자나 소문자일 수 있고, 한 단어 혹은 두 단어일 수 있지만 보통 goto label과 같은 식이다. 제어가 goto 구문에 도달하면, 제어는 표시된 레이블 뒤의 구문으로 점프한다. goto 구문은 데이크스트라를 포함한 많은 컴퓨터 과학자들에 의해 해로운 것으로 간주되어 왔다.

조건부 분기

[편집]

조건문불리언 표현식의 값에 따라 제어를 점프시킨다. 일반적인 변형은 다음과 같다.

if-goto
조건에 따라 레이블로 점프한다. 유사하게 사용되는 기계어 인스트럭션을 밀접하게 모방한 고급 프로그래밍 구문이다.
if-then
점프에 국한되지 않고, 표현식이 참이면 구문이나 블록이 실행된다. then 키워드를 포함하지 않는 언어에서는 이를 if 구문이라고 부를 수 있다.
if-then-else
if-then과 같지만, 조건이 거짓일 때 수행할 두 번째 동작이 있다. then 키워드를 포함하지 않는 언어에서는 이를 if-else 구문이라고 부를 수 있다.
중첩(Nested)
조건문은 종종 다른 조건문 내에 중첩된다.
산술 if
초기 포트란에는 숫자 값이 음수, 0, 또는 양수인지 테스트하는 산술 if(arithmetic if, 일명 3방향 if)가 있었다. 이 구문은 Fortran-90에서 시대에 뒤떨어진 것으로 간주되었고, Fortran 2018부터 삭제되었다.
연산자
일부 언어는 삼항 조건 연산자와 같은 연산자 형태를 가진다.
when 및 unless
은 C 스타일의 ifwhenunless로 보완한다.
메시지
스몰토크는 언어 구조 대신 ifTrueifFalse 메시지를 사용하여 조건문을 구현한다.

다음 파스칼 코드는 간단한 if-then-else를 보여준다. 구문은 에이다에서도 유사하다.

if a > 0 then
  writeln("yes")
else
  writeln("no");

C에서:

if (a > 0) {
    puts("yes");
} else {
    puts("no");
}

배시에서:

if [ $a -gt 0 ]; then
      echo "yes"
else
      echo "no"
fi

파이썬에서:

if a > 0:
    print("yes")
else:
    print("no")

리스프에서:

(princ
  (if (plusp a)
      "yes"
      "no"))

다방향 분기

[편집]

다방향 분기는 일치하는 값에 따라 제어를 점프시킨다. 보통 일치하는 항목이 없을 경우를 대비한 기본 동작 규정이 있다. switch 구문순람표와 같은 컴파일러 최적화를 허용할 수 있다. 동적 언어에서 사례(case)는 상수 표현식에 국한되지 않을 수 있으며, 오른쪽의 셸 스크립트 예제와 같이 글로브를 사용하여 모든 문자열과 일치하는 기본 사례를 구현하는 *)와 같은 패턴 매칭으로 확장될 수 있다. 사례 로직은 SQLdecode 구문과 같이 함수 형태로 구현될 수도 있다.

다음 파스칼 코드는 비교적 간단한 switch 구문을 보여준다. 파스칼은 switch 대신 case 키워드를 사용한다.

case someChar of
  'a': actionOnA;
  'x': actionOnX;
  'y','z':actionOnYandZ;
  else actionOnNoMatch;
end;

에이다에서:

case someChar is
  when 'a' => actionOnA;
  when 'x' => actionOnX;
  when 'y' | 'z' => actionOnYandZ;
  when others => actionOnNoMatch;
end;

C에서:

switch (someChar) {
    case 'a':
        actionOnA;
        break;
    case 'x':
        actionOnX;
        break;
    case 'y':
    case 'z':
        actionOnYandZ;
        break;
    default:
        actionOnNoMatch;
}

배시에서:

case $someChar in
   a)    actionOnA ;;
   x)    actionOnX ;;
   [yz]) actionOnYandZ ;;
   *)    actionOnNoMatch  ;;
esac

리스프에서:

(case some-char
  ((#\a)     action-on-a)
  ((#\x)     action-on-x)
  ((#\y #\z) action-on-y-and-z)
  (else      action-on-no-match))

포트란에서:

select case (someChar)
  case ('a')
    actionOnA
  case ('x')
    actionOnX
  case ('y','z')
    actionOnYandZ
  case default
    actionOnNoMatch
end select

루프

[편집]
프로그램 루프의 기본 유형

루프는 런타임 상태에 따라 여러 번 실행되는 구문의 시퀀스인 루프 본문이다. 본문은 컬렉션의 각 항목에 대해 한 번씩(확정적 반복), 조건이 충족될 때까지(비확정적 반복), 또는 무한히 실행된다. 루프 본문 내부의 루프를 중첩 루프라고 한다.[4][5][6] 루프에서의 조기 종료는 break 구문을 통해 지원될 수 있다.[7][8]

하스켈스킴과 같은 함수형 프로그래밍 언어에서 재귀적 및 반복적 프로세스는 구문적인 루프 구조 대신 꼬리 재귀 프로시저로 표현된다.

숫자 기반

[편집]

비교적 단순하면서도 유용한 루프는 일정 범위의 숫자 값을 반복한다. 단순한 형태는 정수 값에서 시작하여 더 큰 정수 값에서 끝나며 그 사이의 각 정수 값에 대해 반복한다. 종종 증감분은 임의의 정수 값(더 큰 값에서 작은 값으로 루프하기 위해 음수일 수도 있음)이 될 수 있다.

BASIC에서의 예:

FOR I = 1 TO N
   xxx
NEXT I

파스칼에서의 예:

for I := 1 to N do begin
   xxx
end;

포트란에서의 예:

DO I = 1,N
    xxx
END DO

많은 프로그래밍 언어에서 정수만 사용하거나 정수만이 신뢰 가능하다. 부동 소수점 숫자는 하드웨어 제약으로 인해 부정확하게 표현되므로, 다음 루프는 반올림 오류, 하드웨어, 컴파일러와 같은 다양한 요인에 따라 9번 또는 10번 반복될 수 있다. 또한, X의 증가가 반복적인 덧셈에 의해 발생하는 경우 누적된 반올림 오류는 각 반복에서 X의 값이 일반적으로 예상되는 0.1, 0.2, 0.3, ..., 1.0 시퀀스와 상당히 다를 수 있음을 의미한다.

for X := 0.1 step 0.1 to 1.0 do

조건 제어형

[편집]

일부 루프 구조는 조건이 참일 때까지 반복한다. 일부 변형은 루프 시작 부분에서 조건을 테스트하고, 다른 것들은 끝에서 테스트한다. 시작 부분에서 테스트하는 경우 본문을 완전히 건너뛸 수 있다. 끝에서 테스트하는 경우 본문은 항상 적어도 한 번 실행된다.

Visual Basic에서의 예:

DO WHILE (test)
    xxx
LOOP

파스칼에서의 예:

repeat
    xxx
until test;

C 계열의 선행 테스트(pre-test) 예:

while (test) {
    xxx
}

C 계열의 후행 테스트(post-test) 예:

do
    xxx
while (test);

for 키워드를 사용하더라도 3개 부분으로 구성된 C 스타일 루프는 숫자 기반이 아닌 조건 기반 구조다. 두 번째 부분인 조건은 각 루프 전에 평가되므로 루프는 선행 테스트 방식이다. 첫 번째 부분은 상태를 초기화하는 장소이고, 세 번째 부분은 다음 반복을 위해 증감하는 곳이지만, 두 측면 모두 다른 곳에서 수행될 수 있다. 다음 C 코드는 i가 0에서 n-1까지 반복되는 숫자 루프의 로직을 구현한다.

for (int i = 0; i < n; ++i) {
    xxx
}

열거형

[편집]

일부 루프 구조는 컬렉션의 항목을 열거하여 각 항목에 대해 반복한다.

스몰토크에서의 예:

someCollection do: [:eachElement |xxx].

파스칼에서의 예:

for Item in Collection do begin xxx end;

Raku에서의 예:

foreach (item; myCollection) { xxx }

TCL에서의 예:

foreach someArray { xxx }

PHP에서의 예:

foreach ($someArray as $k => $v) { xxx }

자바에서의 예:

Collection<String> coll;
for (String s : coll) {}

C#에서의 예:

foreach (string s in myStringCollection) { xxx }

'foreach'가 'ForEach-Object'의 별칭인 파워셸에서의 예:

someCollection | foreach { $_ }

포트란에서의 예:

forall ( index = first:last:step... )

스칼라에는 컬렉션 제어 루프를 일반화하고 비동기 프로그래밍과 같은 다른 용도도 지원하는 for-표현식(for-expressions)이 있다. 하스켈에는 do-표현식과 컴프리헨션(comprehensions)이 있으며, 이들은 함께 스칼라의 for-표현식과 유사한 기능을 제공한다.

무한

[편집]

무한 루프(infinite loop)는 컴퓨터에서 프로그램이 끝없이 동작하는 것으로, 루프문에 종료 조건이 없거나, 종료 조건과 만날 수 없을 때 생긴다. 다중작업을 지원한 오래된 운영 체제는 한 프로그램의 무한루프에 전체 프로그램이 응답할 수 없게 만들었다.

루프와 절반 문제

[편집]

일반적인 루프 구조는 때때로 반복되는 구문이나 반복되는 조건 등 중복된 코드를 발생시킨다. 이는 다양한 이유로 발생하며, 코드 중복을 제거하거나 최소화하기 위해 제안된 다양한 솔루션이 있다.[9] goto 구문을 사용하는 전통적인 비구조적 솔루션 외에,[10] 일반적인 구조적 솔루션에는 루프 내부에 조건문(if 구문)을 두는 것(조건은 중복될 수 있지만 구문은 중복되지 않음)이나 반복되는 로직을 함수로 래핑하는 것(함수 호출은 중복되지만 구문은 중복되지 않음)이 포함된다.[9]

일반적인 경우는 루프의 시작 부분은 항상 실행되지만, 마지막 반복에서는 끝 부분이 건너뛰어질 수 있는 경우다.[10] 이는 데이크스트라에 의해 "n번과 절반" 수행되는 루프라고 불렸으며,[11] 현재는 루프와 절반(loop-and-a-half) 문제라고 불린다.[8] 일반적인 예로는 첫 번째 부분에서 데이터를 읽고, 데이터의 끝을 확인한 다음, 두 번째 부분에서 데이터를 처리하는 경우, 또는 처리하고 끝을 확인한 다음 다음 반복을 준비하는 경우가 있다.[10][8] 이러한 경우 루프의 첫 번째 부분은 번 실행되지만, 두 번째 부분은 번만 실행된다.

이 문제는 적어도 1967년 커누스에 의해 인식되었으며, 비르트(Wirth)는 조기 루프 종료를 통해 이를 해결할 것을 제안했다.[12] 1990년대 이후로 이는 다음과 같이 break 구문을 사용하는 가장 일반적으로 가르치는 솔루션이 되었다.[8]

'loop
    statements
    if condition break
    statements
repeat

이 솔루션의 미묘한 차이점은 조건이 일반적인 while 조건의 반대라는 것이다. 중간에 탈출구가 있는 while condition ... repeat를 다시 작성하려면 조건을 반전시켜야 한다: loop ... if not condition exit ... repeat. 중간 테스트 루프 제어 구조는 조건을 반전시키지 않고도 루프와 절반 사용 사례를 명시적으로 지원한다.[12]

비구조적

[편집]

루프 구조는 다른 반복을 발생시키거나 루프 구문 이후에 실행을 계속하게 하는 구조화된 완료 기준을 제공한다. 하지만 많은 언어에서 다양한 비구조적 제어 흐름 구조가 지원된다.

조기 다음 반복
일부 언어는 다음 반복을 위해 제어를 루프 본문의 시작 부분으로 점프시키는 구조를 제공한다. 예를 들어, continue(가장 일반적), skip,[13] cycle(포트란), 또는 next(펄 및 루비)가 있다.
반복 재실행
[14]이나 루비[15]와 같은 일부 언어에는 동일한 반복에 대해 본문의 시작으로 점프하는 redo 구문이 있다.
재시작
루비에는 첫 번째 반복부터 전체 루프를 다시 시작하는 retry 구문이 있다.[16]
조기 종료
[편집]

조기 종료는 제어를 루프 본문 이후로 점프시킨다.[17][8] 예를 들어 목록을 검색할 때 항목이 발견되면 반복을 멈출 수 있다. 일부 프로그래밍 언어는 break(대부분의 언어), Exit(Visual Basic), 또는 last(펄)와 같은 구문을 제공한다.

다음 에이다 코드에서 루프는 X가 0일 때 종료된다.

loop
    Get(X);
    if X = 0 then
        exit;
    end if;
    DoSomething(X);
end loop;

더 관용적인 스타일은 exit when을 사용하는 것이다.

loop
    Get(X);
    exit when X = 0;
    DoSomething(X);
end loop;

파이썬은 루프와 함께 else-절을 사용하여 루프가 조기에 종료되었는지(break 구문 사용) 여부에 따라 코드의 조건부 실행을 지원한다. 다음 파이썬 코드에서 else 절은 내부의 if 구문이 아니라 for 구문에 연결되어 있다. 파이썬의 forwhile 루프는 모두 이러한 else 절을 지원하며, 이는 루프의 조기 종료가 발생하지 않은 경우에만 실행된다.

for n in set_of_numbers:
    if isprime(n):
        print("Set contains a prime number")
        break
else:
    print("Set did not contain any prime numbers")
다단계 중단
[편집]

일부 언어는 중첩된 루프에서 벗어나는 것을 지원한다. 이론적으로 이를 다단계 중단(multi-level breaks)이라고 한다. 한 가지 일반적인 사용 예는 다차원 테이블을 검색하는 것이다. 이는 배시[18] 및 PHP[19]에서처럼 다단계 중단(N개 레벨 밖으로 탈출)을 통해 수행하거나, 에이다, Go, 자바, 러스트 및 펄[20]에서처럼 레이블이 지정된 중단(탈출하여 주어진 레이블에서 계속)을 통해 수행될 수 있다. 다단계 중단에 대한 대안으로는 다른 레벨을 벗어나기 위해 테스트되는 상태 변수와 함께 단일 중단을 사용하거나, 벗어날 레벨에서 포착되는 예외를 사용하거나, 중첩 루프를 함수에 넣고 return을 사용하여 전체 중첩 루프의 종료를 실행하거나, 레이블과 goto 구문을 사용하는 것이 있다. C와 C++은 현재 다단계 중단이나 이름이 지정된 루프를 지원하지 않으며, 일반적인 대안은 goto를 사용하여 레이블이 지정된 중단을 구현하는 것이다.[21] 그러나 이 기능의 포함이 제안되었으며,[22] 자바 구문을 따라 C2Y에 추가되었다.[23] 파이썬은 다단계 중단이나 continue를 지원하지 않는다. 이는 PEP 3136에서 제안되었으나, 추가되는 복잡성이 드문 정당한 사용 사례만큼의 가치가 없다는 근거로 거부되었다.[24]

다단계 중단 개념은 오늘날 코사라주 계층(Kosaraju hierarchy)이라고 불리는 것을 발생시키기 때문에 이론 컴퓨터 과학에서 어느 정도 관심의 대상이다.[25] 1973년 S. Rao Kosaraju는 루프에서 임의 깊이의 다단계 중단이 허용되는 한, 구조적 프로그래밍에서 추가 변수 도입을 피할 수 있음을 증명함으로써 구조화 정리를 정교화했다.[26] 또한, 코사라주는 프로그램의 엄격한 계층 구조가 존재함을 증명했다. 모든 정수 n에 대해, 추가 변수를 도입하지 않고는 깊이가 n 미만인 다단계 중단을 가진 프로그램으로 다시 작성할 수 없는 깊이 n의 다단계 중단을 포함하는 프로그램이 존재한다.[25]

데이비드 와트(David Watt)는 2004년 자신의 교과서에서 다단계 중단과 return 구문 사이의 유사성을 설명하기 위해 테넌트(Tennent)의 시퀀서(sequencer) 개념을 사용한다. 와트는 "텍스트적으로 감싸는 명령이나 프로시저의 실행을 종료하는 시퀀서"로 정의되는 탈출 시퀀서(escape sequencers) 클래스가 루프에서의 중단(다단계 중단 포함)과 return 구문을 모두 포괄한다고 지적한다. 그러나 일반적으로 구현되는 것처럼 return 시퀀서는 (반환) 값을 가질 수도 있는 반면, 현대 언어에서 구현되는 중단 시퀀서는 대개 그럴 수 없다.[27]

중간 테스트
[편집]

다음 구조는 1972년 올레요한 달에 의해 제안되었다.[28]

   loop                           loop
       xxx1                           read(char);
   while test;                    while not atEndOfFile;
       xxx2                           write(char);
   repeat;                        repeat;

여기서의 구조는 중간에 while 체크가 있는 do 루프로 생각할 수 있으며, 이는 명확한 루프와 절반 로직을 허용한다. 또한 개별 구성 요소를 생략함으로써 이 단일 구조는 대부분의 프로그래밍 언어에서 여러 구조를 대체할 수 있다. xxx1이 생략되면 상단에서 테스트하는 루프(전통적인 while 루프)를 얻는다. xxx2가 생략되면 하단에서 테스트하는 루프를 얻으며, 이는 많은 언어의 do while 루프와 동일하다. while이 생략되면 무한 루프를 얻는다. 이 구조는 또한 조기 종료와 달리 조건의 극성을 동일하게 유지할 수 있게 해주며(조기 종료는 not을 추가하여 극성을 반전시켜야 함),[12] while 대신 until로 작동하게 할 수도 있다.

이 구조는 널리 지원되지 않으며, 대부분의 언어는 대신 조건부 조기 종료를 위해 if ... break를 사용한다.

이는 포스와 같은 일부 언어에서 지원되며 구문은 BEGIN ... WHILE ... REPEAT이다.[29] 또한 셸 스크립트 언어인 본 셸(sh) 및 배시에서도 지원되며 구문은 while ... do ... done 또는 until ... do ... done이다.[30][31]

while
  statement-1
  statement-2
  ...
  condition
do
  statement-a
  statement-b
  ...
done

셸 구문이 작동하는 이유는 while(또는 until) 루프가 명령 목록을 조건으로 수용하기 때문이다.[32] 형식적으로는 다음과 같다.

 while test-commands; do consequent-commands; done

test-commands 목록의 값(종료 상태)은 마지막 명령의 값이며, 이들은 줄바꿈으로 구분될 수 있어 위의 관용적 형태가 결과로 나타난다.

C와 C++에서는 쉼표 연산자를 사용하여 유사한 구조를 만들 수 있으며, 유사한 구조를 가진 다른 언어에서도 while 조건 안에 구문 목록을 밀어넣을 수 있다.

while (statement_1, statement_2, condition) {
    statement_a;
    statement_b;
}

이것이 문법적으로는 올바르지만, 권장되지는 않으며 주로 짧은 '수정 후 테스트' 사례에만 사용된다.[33]

while (read_string(s), strlen(s) > 0) {
    // ...
}

루프 변동량 및 불변량

[편집]

루프 변동량루프 불변성은 루프의 올바름을 표현하는 데 사용된다.[34]

실용적인 관점에서 루프 변동량은 초기 비음수 값을 갖는 정수 표현식이다. 변동량의 값은 각 루프 반복 동안 감소해야 하지만 루프의 올바른 실행 동안 절대 음수가 되어서는 안 된다. 루프 변동량은 루프가 종료됨을 보장하는 데 사용된다.

루프 불변량은 첫 번째 루프 반복 전에 참이어야 하고 각 반복 후에도 참으로 유지되어야 하는 단언(assertion)이다. 이는 루프가 올바르게 종료될 때 종료 조건과 루프 불변량이 모두 충족됨을 의미한다. 루프 불변량은 연속적인 반복 동안 루프의 특정 속성을 모니터링하는 데 사용된다.

에펠과 같은 일부 프로그래밍 언어는 루프 변동량 및 불변량에 대한 기본 지원을 포함한다. 다른 경우에는 자바루프 구문에 대한 자바 모델링 언어(Java Modeling Language) 사양과 같이 애드온 형태로 지원된다.

루프 하위 언어

[편집]

일부 리스프 방언은 루프를 설명하기 위한 광범위한 하위 언어를 제공한다. 초기 예제는 Interlisp의 Conversional Lisp에서 찾을 수 있다. 커먼 리스프[35]는 그러한 하위 언어를 구현하는 Loop 매크로를 제공한다.

루프 시스템 상호 참조표

[편집]
프로그래밍 언어 조건부 루프 조기 종료 루프 계속 재실행 재시도 올바름 시설
시작 중간 종료 숫자 컬렉션 일반 무한 [1] 변동량 불변량
에이다 배열 아니요 심층 중첩 아니요
APL 아니요 심층 중첩 [3] 아니요 아니요
C 아니요 아니요 [2] 아니요 아니요 심층 중첩 [3] 심층 중첩 [3] 아니요 아니요 아니요 아니요
C++ 아니요 아니요 [2] [9] 아니요 심층 중첩 [3] 심층 중첩 [3] 아니요 아니요 아니요 아니요
C# 아니요 아니요 [2] 아니요 심층 중첩 [3] 심층 중첩 [3] 아니요 아니요 아니요 아니요
코볼 아니요 아니요 아니요 심층 중첩 [15] 심층 중첩 [14] 아니요
커먼 리스프 내장 전용 [16] 심층 중첩 아니요
D 아니요 [14] 심층 중첩 심층 중첩 아니요
에펠 아니요 아니요 [10] 아니요 단일 레벨 [10] 아니요 아니요 아니요 [11] 정수 전용 [13]
F# 아니요 아니요 아니요 아니요 아니요 [6] 아니요 아니요
FORTRAN 77 아니요 아니요 아니요 아니요 아니요 단일 레벨 아니요 아니요
Fortran 90 아니요 아니요 아니요 아니요 심층 중첩 심층 중첩 아니요 아니요
포트란 95 이후 아니요 아니요 배열 아니요 심층 중첩 심층 중첩 아니요 아니요
Go 아니요 아니요 내장 전용 심층 중첩 심층 중첩 아니요
하스켈 아니요 아니요 아니요 아니요 아니요 아니요 [6] 아니요 아니요
자바 아니요 아니요 [2] 아니요 심층 중첩 심층 중첩 아니요 비내장 [12] 비내장 [12]
자바스크립트 아니요 아니요 [2] 아니요 심층 중첩 심층 중첩 아니요 아니요 아니요
코틀린 아니요 아마도 아니요 아니요 심층 중첩 심층 중첩 아니요 아니요 아니요 아니요
Natural 아니요 아니요
OCaml 아니요 아니요 배열, 리스트 아니요 아니요 아니요 [6] 아니요 아니요
Odin 아니요 [17] 아니요 아니요 아니요 [5] 내장 전용 아니요 [17] 심층 중첩 심층 중첩
PHP 아니요 아니요 [2] [5] [4] 아니요 심층 중첩 심층 중첩 아니요 아니요 아니요 아니요
아니요 아니요 [2] [5] 아니요 심층 중첩 심층 중첩
파이썬 아니요 아니요 아니요 [5] 아니요 아니요 심층 중첩 [6] 심층 중첩 [6] 아니요 아니요 아니요 아니요
QB64 아니요 아니요 루프 유형별 단일 레벨 루프 유형별 단일 레벨 아니요 아니요 아니요 아니요
Rebol 아니요 [7] 아니요 [8] 단일 레벨 [6] 아니요 아니요
루비 아니요 아니요 심층 중첩 [6] 심층 중첩 [6]
러스트 아니요 아니요 아니요 [5] 아니요 심층 중첩 심층 중첩 아니요 아니요 아니요 아니요
표준 ML 아니요 아니요 아니요 배열, 리스트 아니요 아니요 아니요 [6] 아니요 아니요
스위프트 아니요 아니요 아니요 심층 중첩 심층 중첩 아니요 아니요 아니요 아니요
Visual Basic .NET 아니요 아니요 루프 유형별 단일 레벨 루프 유형별 단일 레벨 아니요 아니요 아니요 아니요
파워셸 아니요 아니요 [2] 아니요 아니요 아니요 아니요 아니요
Zig 아니요 아니요 아니요 [5] 내장 전용 아니요 아니요 심층 중첩 심층 중첩 아니요 아니요 아니요 아니요
  1. a while (true)는 전용 언어 구조가 아니므로 이 목적의 무한 루프로 간주하지 않는다.
  2. a b c d e f g h C의 for (init; test; increment) 루프는 종종 카운팅을 위해 사용되지만, 구체적으로 카운팅 전용이 아닌 일반적인 루프 구조다.
  3. a b c 심층 중단은 레이블과 goto의 사용을 통해 APL, C, C++ 및 C#에서 달성될 수 있다.
  4. a 객체에 대한 반복은 PHP 5에서 추가되었다.
  5. a b c d e f 카운팅 루프는 증가하는 목록이나 제너레이터를 반복함으로써 시뮬레이션될 수 있다(예: 파이썬의 range()).
  6. a b c d e 심층 중단은 예외 처리의 사용을 통해 달성될 수 있다.
  7. a while 함수를 이 용도로 사용할 수 있으므로 특별한 구조가 없다.
  8. a 특별한 구조는 없으나 사용자가 일반적인 루프 함수를 정의할 수 있다.
  9. a C++11 표준은 범위 기반 for(range-based for)를 도입했다. STL에는 STL 컨테이너를 반복하고 각 요소에 대해 단항 함수를 호출할 수 있는 std::for_each 템플릿 함수가 있다.[36] 이 기능은 이러한 컨테이너에 대한 매크로로도 구성될 수 있다.[37]
  10. a 숫자 루프는 정수 간격에 걸친 반복을 통해 이루어지며, 조기 종료는 종료를 위한 추가 조건을 포함함으로써 수행된다.
  11. a 에펠은 예약어 retry를 지원하지만, 루프 제어가 아닌 예외 처리에서 사용된다.
  12. a 자바 모델링 넝언어(Java Modeling Language, JML) 행동 인터페이스 사양 언어가 필요하다.
  13. a 루프 변동량이 정수여야 한다. 초한(transfinite) 변동량은 지원되지 않는다. Eiffel: Why loop variants are integers
  14. a D는 무한 컬렉션과 해당 컬렉션을 반복하는 기능을 지원한다. 이는 특별한 구조를 필요로 하지 않는다.
  15. a 심층 중단은 GO TO와 프로시저를 사용하여 달성할 수 있다.
  16. a 커먼 리스프는 일반 컬렉션 유형 개념보다 먼저 등장했다.
  17. a b Odin의 일반 for 루프는 조건부 루프와 무한 루프를 위한 구문 단축키를 지원한다.

비로컬

[편집]

특히 더 동적인 프로그래밍 스타일을 선호하는 많은 프로그래밍 언어는 현재 실행 지점에서 사전 선언된 지점으로 실행이 점프하게 하는 비로컬(non-local) 제어 흐름 구조를 제공한다. 주목할 만한 예는 다음과 같다.

조건 처리

[편집]

가장 초기 포트란 컴파일러는 IF ACCUMULATOR OVERFLOW, IF QUOTIENT OVERFLOW, 및 IF DIVIDE CHECK를 포함하여 예외적인 조건을 처리하기 위한 구문을 지원했다. 머신 독립성을 위해 이들은 FORTRAN IV 및 Fortran 66 표준에 포함되지 않았다. 그러나 Fortran 2003 이후로 IEEE_EXCEPTIONS 모듈의 함수 호출을 통해 수치 문제를 테스트하는 것이 가능해졌다.

PL/I에는 발생할 수 있고 ON condition action; 에 의해 가로챌 수 있는 약 22개의 표준 조건(예: ZERODIVIDE, SUBSCRIPTRANGE, ENDFILE)이 있다. 프로그래머는 자신만의 명명된 조건을 정의하고 사용할 수도 있다.

비구조적 if와 마찬가지로 하나의 구문만 지정할 수 있으므로 많은 경우 제어 흐름이 재개되어야 하는 위치를 결정하기 위해 GOTO가 필요하다.

안타깝게도 일부 구현은 공간과 시간 모두에서 상당한 오버헤드(특히 SUBSCRIPTRANGE)를 가졌기 때문에 많은 프로그래머가 조건 사용을 피하려고 노력했다.

일반적인 구문 예:

ON condition GOTO label

예외 처리

[편집]

많은 현대 언어는 기본적으로 예외 처리를 지원한다. 일반적으로 예외적인 제어 흐름은 예외 객체가 던져지는(발생하는) 것으로 시작된다. 제어는 콜 스택의 가장 안쪽 예외 핸들러로 진행된다. 핸들러가 예외를 처리하면 흐름 제어는 정상으로 돌아간다. 그렇지 않으면 핸들러 중 하나가 예외를 처리할 때까지 또는 프로그램이 가장 바깥쪽 범위에 도달하여 종료될 때까지 제어가 포함하는 핸들러로 바깥쪽으로 진행된다. 제어가 점차 바깥쪽 핸들러로 흐르면서 콜 스택을 팝하는 것과 같이 정상적으로 발생할 측면들이 자동으로 처리된다.

다음 C++ 코드는 구조적 예외 처리를 보여준다. doSomething() 실행에서 예외가 전파되고 예외 객체 유형이 catch 절에 지정된 유형 중 하나와 일치하면 해당 절이 실행된다. 예를 들어, SomeException 유형의 예외가 doSomething()에 의해 전파되면 제어는 2행에서 4행으로 점프하고 "Caught SomeException" 메시지가 출력된 다음 제어는 try 구문 이후인 8행으로 점프한다. 다른 유형의 예외가 전파되면 제어는 2행에서 6행으로 점프한다. 예외가 없으면 제어는 2행에서 8행으로 점프한다.

try {
    doSomething();
} catch (const SomeException& e)
    std::println("Caught SomeException: {}", e.what());
} catch (...) {
    std::println("Unknown error");
}
doNextThing();

많은 언어가 C++ 키워드(throw, trycatch)를 사용하지만 일부 언어는 다른 키워드를 사용한다. 예를 들어 에이다는 예외 핸들러를 도입하기 위해 exception을 사용하고 catch 대신 when을 사용한다. 애플스크립트는 다음 애플스크립트 코드에 표시된 것처럼 예외에 대한 정보를 추출하기 위해 구문에 플레이스홀더를 통합한다.

try
    set myNumber to myNumber / 0
on error e number n from f to t partial result pr
    if ( e = "Can't divide by zero" ) then display dialog "You must not do that"
end try

많은 언어(Object Pascal, D, 자바, C#, 파이썬 포함)에서 try 구문의 끝에 있는 finally 절은 try의 나머지 부분에서 예외가 전파되든 아니든 try 구문의 끝에서 실행된다. 다음 C# 코드는 스트림 stream이 닫히도록 보장한다.

FileStream stream = null;
try
{
    stream = new FileStream("logfile.txt", FileMode.Create);
    return ProcessStuff(stream);
}
finally
{
    if (stream != null)
    {
        stream.Close();
    }
}

이 패턴은 일반적이기 때문에 C#은 정리를 보장하기 위해 using 구문을 제공한다. 다음 코드에서 ProcessStuff()가 예외를 전파하더라도 stream 객체는 해제된다. 파이썬의 with 구문과 File.open에 대한 루비의 블록 인수는 유사한 효과를 위해 사용된다.

using (FileStream stream = new("logfile.txt", FileMode.Create))
{
    return ProcessStuff(stream);
}

컨티뉴에이션

[편집]

제너레이터

[편집]

코루틴

[편집]

코루틴(coroutine)은 루틴의 일종으로서, 협동 루틴이라 할 수 있다(코루틴의 "Co"는 with 또는 together를 뜻한다). 상호 연계 프로그램을 일컫는다고도 표현가능하다. 루틴과 서브 루틴은 서로 비대칭적인 관계이지만, 코루틴들은 완전히 대칭적인, 즉 서로가 서로를 호출하는 관계이다. 코루틴들에서는 무엇이 무엇의 서브루틴인지를 구분하는 것이 불가능하다. 코루틴 A와 B가 있다고 할 때, A를 프로그래밍 할 때는 B를 A의 서브루틴으로 생각한다. 그러나 B를 프로그래밍할 때는 A가 B의 서브루틴이라고 생각한다. 어떠한 코루틴이 발동될 때마다 해당 코루틴은 이전에 자신의 실행이 마지막으로 중단되었던 지점 다음의 장소에서 실행을 재개한다.[38]

도널드 커누스에 따르면 멜빈 콘웨이는 1958년 코루틴(coroutine)이라는 용어를 만들어냈으며 당시 그는 이를 어셈블리 프로그램에 적용했다.[39] 코루틴에 관해 설명된 최초의 출판물은 1963년에 등장하였다.[40]

COMEFROM

[편집]

중첩 루프에서의 이벤트 기반 조기 종료

[편집]

잔 구성체(Zahn's construct)는 1974년에 제안되었으며,[41] Knuth (1974)에서 논의되었다. 수정된 버전이 여기에 제시된다.

   exitwhen EventA or EventB or EventC;
       xxx
   exits
       EventA: actionA
       EventB: actionB
       EventC: actionC
   endexit;

exitwhenxxx 내에서 발생할 수 있는 이벤트를 지정하는 데 사용되며, 발생은 이벤트 이름을 구문으로 사용함으로써 표시된다. 어떤 이벤트가 발생하면 관련 동작이 수행된 다음 제어가 endexit 바로 뒤로 전달된다. 이 구조는 어떤 상황이 적용됨을 결정하는 것과 해당 상황에 대해 취할 조치 사이에 매우 명확한 분리를 제공한다.

exitwhen은 개념적으로 예외 처리와 유사하며, 많은 언어에서 예외나 유사한 구조가 이 목적으로 사용된다.

다음 단순한 예제는 특정 항목을 위해 2차원 테이블을 검색하는 것을 포함한다.

   exitwhen found or missing;
       for I := 1 to N do
           for J := 1 to M do
               if table[I,J] = target then found;
       missing;
   exits
       found:   print ("item is in table");
       missing: print ("item is not in table");
   endexit;

같이 보기

[편집]

각주

[편집]
  1. Payer, Mathias; Kuznetsov, Volodymyr. On differences between the CFI, CPS, and CPI properties. nebelwelt.net. 2016년 6월 1일에 확인함.
  2. Adobe Flash Bug Discovery Leads To New Attack Mitigation Method. Dark Reading. 2015년 11월 10일. 2016년 6월 1일에 확인함.
  3. Endgame. Endgame to Present at Black Hat USA 2016 (보도 자료). www.prnewswire.com. 2016년 6월 1일에 확인함.
  4. Nested Loops in C with Examples (미국 영어). GeeksforGeeks. 2019년 11월 25일. 2024년 3월 14일에 확인함.
  5. Python Nested Loops (미국 영어). www.w3schools.com. 2024년 3월 14일에 확인함.
  6. Dean, Jenna (2019년 11월 22일). Nested Loops (영어). The Startup. 2024년 3월 14일에 확인함.
  7. Knuth, Donald E. (1974). Structured Programming with go to Statements. Computing Surveys 6. 261–301쪽. CiteSeerX 10.1.1.103.6084. doi:10.1145/356635.356640. S2CID 207630080.
  8. 1 2 3 4 5 Roberts, E. [1995] "Loop Exits and Structured Programming: Reopening the Debate 보관됨 2014-07-25 - 웨이백 머신," ACM SIGCSE Bulletin, (27)1: 268–272.
  9. 1 2 Messy Loop Conditions. WikiWikiWeb. 2014년 11월 3일.
  10. 1 2 3 Knuth 1974, 278쪽, Simple Iterations.
  11. 에츠허르 데이크스트라, 1974-01-03에 도널드 커누스에게 보낸 개인 서신, Knuth (1974, 278쪽, Simple Iterations)에서 인용됨
  12. 1 2 3 Knuth 1974, 279쪽.
  13. What is a loop and how we can use them?. 2020년 7월 28일에 원본 문서에서 보존된 문서. 2020년 5월 25일에 확인함.
  14. redo - perldoc.perl.org. perldoc.perl.org. 2020년 9월 25일에 확인함.
  15. control_expressions - Documentation for Ruby 2.4.0. docs.ruby-lang.org. 2020년 9월 25일에 확인함.
  16. control_expressions - Documentation for Ruby 2.3.0. docs.ruby-lang.org. 2020년 9월 25일에 확인함.
  17. 루프와 절반 문제를 해결하는 일반적인 방법이다.
  18. Advanced Bash Scripting Guide: 11.3. Loop Control
  19. PHP Manual: "break"
  20. perldoc: last
  21. comp.lang.c FAQ list · "Question 20.20b"
  22. named-loops. open-std.org. 2024년 9월 18일.
  23. Information technology — Programming languages — C (PDF). open-std.org. 2025년 5월 4일.
  24. [Python-3000] Announcing PEP 3136, Guido van Rossum
  25. 1 2 Kozen, Dexter (2008). The Böhm–Jacopini Theorem is False, Propositionally. Mathematics of Program Construction (PDF). Lecture Notes in Computer Science 5133. 177–192쪽. CiteSeerX 10.1.1.218.9241. doi:10.1007/978-3-540-70594-9_11. ISBN 978-3-540-70593-2.
  26. Kosaraju, S. Rao. "Analysis of structured programs," Proc. Fifth Annual ACM Syrup. Theory of Computing, (May 1973), 240-252; also in J. Computer and System Sciences, 9, 3 (December 1974), Knuth (1974)에서 인용됨.
  27. David Anthony Watt; William Findlay (2004). Programming language design concepts. John Wiley & Sons. 215–221쪽. ISBN 978-0-470-85320-7.
  28. Dahl & Dijkstra & Hoare, "Structured Programming" Academic Press, 1972.
  29. 6. Throw It For a Loop.
  30. 3.2.5.1 Looping Constructs, The GNU Bash Reference Manual, 2025년 5월 18일
  31. How could a language make the loop-and-a-half less error-prone?. Stack Exchange: Programming Language Design and Implementation.
  32. 3.2.4 Lists of Commands, The GNU Bash Reference Manual, 2025년 5월 18일
  33. What does the comma operator , do?. Stack Overflow.
  34. Meyer, Bertrand (1991). Eiffel: The Language. Prentice Hall. 129–131쪽.
  35. Common Lisp LOOP macro.
  36. for_each. Sgi.com. Retrieved on 2010-11-09.
  37. Chapter 1. Boost.Foreach 보관됨 2010-01-29 - 웨이백 머신. Boost-sandbox.sourceforge.net (2009-12-19). Retrieved on 2010-11-09.
  38. The Art of Computer programming, 도널드 커누스 저
  39. Knuth, Donald Ervin (1997). Fundamental Algorithms 3판. The Art of Computer Programming 1. Addison-Wesley. Section 1.4.5: History and Bibliography, pp. 229. ISBN 978-0-201-89683-1.
  40. Conway, M. E. (July 1963). Design of a Separable Transition-Diagram Compiler. Communications of the ACM 6 (7): 396408. doi:10.1145/366663.366704.
  41. Zahn, C. T. "A control statement for natural top-down structured programming" presented at Symposium on Programming Languages, Paris, 1974.

외부 링크

[편집]