프로세서의 제어 흐름, 기계어와 어셈블리어, Fortran과 Algol 60, 조건문과 반복문, 반복·블록의 조기 종료, 그리고 추가 참고 문헌까지 초기 프로그래밍 언어와 제어 구조의 발전을 살펴본다.
제1장 초기 프로그래밍 언어
오늘날 대부분의 연산 장치는 저장 프로그램 방식의 프로그래머블 컴퓨터로, 계산을 기술하는 프로그램이 그 프로그램이 작동하는 데이터와 함께 메모리에 저장된다. 이러한 컴퓨터 구조는 “폰 노이만 아키텍처”로 알려져 있는데, 이는 von Neumann (1945)의 기술 보고서에서 처음 기술되었기 때문이다. 유사한 아키텍처가 Turing (1946)에 의해서도 설명된다.
프로그램 카운터와 분기.폰 노이만 아키텍처에서 중앙처리장치(CPU)는 특별한 레지스터인 프로그램 카운터(PC)를 가지며, 다음에 실행할 명령이 들어 있는 메모리 워드의 주소를 담고 있다. 대부분의 명령(산술 및 논리 연산, 메모리 읽기/쓰기 등)은 실행 시 PC를 증가시켜 메모리의 다음 명령을 가리키게 한다. 이는 메모리에 저장된 명령들의 순차적 실행을 보장한다. 반면, 분기(branch) 명령은 PC의 값을 명시적으로 설정하여 프로그램의 임의 지점으로 점프할 수 있게 한다.
예를 들어, 다음의 x86 프로세서 계열용 기계 코드는 출력 포트 32로 팩토리얼 수 1!, 2!, …, n!, …을 출력한다. (실제 기계 코드는 2번째 열의 16진수 숫자이며, 3번째 열은 이를 이해하기 위한 디스어셈블리이다.)
0: b8 01 00 00 00 mov eax, 1 ; eax에는 n!가 들어 있음 5: ba 01 00 00 00 mov edx, 1 ; edx에는 n이 들어 있음 10: e7 40 out 32, eax 12: 83 c2 01 add edx, 1 15: 0f af c2 imul eax, edx 18: eb f6 jmp 10
처음 다섯 개의 명령이 순서대로 실행되는 동안 PC는 차례대로 0, 5, 10, 12, 15, 18 값을 갖는다. 두 바이트 eb f6로 인코딩된 jmp 10 명령은 PC를 다시 10으로 되돌려 out, add, imul 명령을 감싸는 무한 루프를 만든다.
조건부 분기.현대 프로세서는 조건부 분기 명령을 제공한다. 이는 레지스터에 대한 어떤 조건이 참이면 PC를 지정된 값으로 설정하고, 거짓이면 순차적으로 계속 진행한다.
예를 들어, 다음은 레지스터 edx에 주어진 n에 대해 n!를 계산하고 이를 출력하는 카운트드 루프이다:
0: b8 01 00 00 00 mov eax, 1 5: 0f af c2 imul eax, edx 8: 83 ea 01 sub edx, 1 11: 75 f8 jnz 5 ; 0이 아니면 5로 점프 13: e7 40 out 32, eax
jnz로 쓰인 명령은 이전 결과가 0이 아니면 분기하고, 그렇지 않으면 순차적으로 계속한다. 이로써 imul/sub 루프 본문이 edx 레지스터가 0이 될 때까지 n번 실행된다. 그런 다음 실행은 jnz 다음의 out 명령으로 이어진다.
역사적 대안: 자기 수정 코드.조건부 분기 명령은 폰 노이만과 튜링의 원래 설계에는 포함되지 않았다. 대신 두 저자는 코드가 메모리에 저장되며, 따라서 적절한 메모리 위치에 기록하여 프로그램 실행 중에 수정될 수 있다는 사실에 의존한다. 이를 자기 수정 코드(self-modifying code)라고 한다.
예를 들어, 레지스터 edx가 1일 때는 100바이트 앞으로 점프하고 edx가 0일 때는 순차 실행을 계속하도록, 적절한 jmp +100 또는 jmp +0 명령을 합성한 뒤 곧바로 실행하는 방법은 다음과 같다.
0: 6b c2 64 imul eax, edx, 100 3: 88 05 01 00 00 00 mov 1(rip), al 9: eb ?? jmp ?? ; 실행 중에 동적으로 수정됨
eb 오피코드 다음의 바이트는 분기의 목표에 대한 상대 주소이다. mov 명령은 edx가 0이면 0을, edx가 1이면 100을 이 바이트에 쓴다. 그 뒤 PC를 증가시키고 9의 jmp 명령을 실행한다. 그러면 edx가 0일 때는 PC 11로, edx가 1일 때는 PC 111로 분기한다.
자기 수정 코드는 구성하기 어렵고 파이프라인 처리기에서 성능을 저하한다. 이런 이유로 1950년대 동안 조건부 분기 명령과 인덱스드 메모리 주소 지정이 프로세서 명령 집합의 표준이 되었고, 자기 수정 코드의 두 가지 일반적 용도를 대체했다.
그림 1.1: Power 명령 집합 아키텍처의 “조건 분기(branch conditional)” 명령. (출처: OpenPOWER Foundation, Power Instruction Set Architecture, 버전 3.1C, 2024.)
기계어는 CPU 회로가 하드웨어에서 직접 실행할 수 있는 프로그래밍 언어이다. 기계어 프로그램은 실행 중 메모리에 저장되는 프로그램 명령들의 비트 수준 인코딩으로 구성된다. 각 명령 집합 아키텍처(ISA)는 자신의 기계어를 정의한다. 많은 ISA는 각 명령을 32비트 워드로 인코딩하며, 가변 길이 인코딩을 쓰는 ISA도 있다. 인코딩은 ISA 레퍼런스 매뉴얼에 문서화되어 있다. 예를 들어, 그림1.1은 Power ISA의 조건 분기 명령 인코딩을 보여준다. 이 명령을 인코딩하는 32비트 워드에서 일부 비트(오피코드)는 고정되어 있으며 해당 명령을 식별한다. 다른 비트들은 분기 조건, 분기 대상, 그리고 LR와 CTR 레지스터에 대한 가능한 부수 효과를 인코딩한다.
어셈블리어는 기계어의 텍스트 표현으로, 사람이 쓰고 읽기 훨씬 쉽지만 기계어와 충분히 가까워서 어셈블러(assembler)라고 하는 간단한 프로그램이 어셈블리어를 실행 가능한 기계 코드로 자동 변환할 수 있다. 최초의 어셈블러는 1947년 Kathleen Booth의 ARC 컴퓨터 작업에서 등장했다.
어셈블리어는 프로세서 명령을 이름 붙이기 위해 니모닉을 사용하고(위 예시의 mov, imul, jmp 같은 니모닉), 프로그램 지점과 분기 대상을 이름 붙이기 위해 레이블을 사용한다. 또한 코드를 문서화하기 위한 주석을 지원한다. 주석은 어셈블러에서 무시되지만 사람이 코드를 이해하고 유지보수하는 데 큰 도움이 된다.
다음은 위의 n! 계산을 x86 어셈블리어로 다시 쓴 것이다. 주석은 세미콜론 문자 “;”로 시작한다.
; n의 팩토리얼을 계산한다.
; n은 edx로 전달된다.
; n!는 eax에 남는다.
; edx는 덮어쓴다.
mov eax, 1 ; eax를 1로 초기화
again: imul eax, edx ; eax에 n을 곱함
dec edx ; n을 1 감소
jnz again ; n이 0이 아닐 동안 반복
; 여기서 eax에는 n!가 들어 있음
imul 명령의 위치를 나타내는 심볼 레이블 again을 사용하여, 조건 분기 jnz again처럼 해당 위치로 점프를 쉽게 표현할 수 있음을 주목하라.
기계어보다 훨씬 다루기 쉽지만, 어셈블리어는 여전히 명령 집합 아키텍처와 일대일 대응을 이룬다. 각 오피코드는 프로세서가 지원하는 하나의 명령에 대응하고, 각 소스 텍스트 줄은 하나의 이진 인코딩된 기계 명령을 생성한다.
매크로 어셈블러 또는 오토코더(IBM의 용어)은 프로세서 명령에 대응하는 오피코드 외에도 추가 오피코드를 지원하는 정교한 어셈블러로, 프로그래머에게 더 풍부한 어셈블리어를 제공한다. 이러한 확장 오피코드(매크로 명령이라고도 함)는 기계 코드로의 변환 중에 준비된 프로세서 명령 시퀀스로 확장된다.
매크로 명령은 다양한 종류의 반복문을 간결하고 명확하게 작성하는 데 자주 쓰인다. 예컨대 레지스터 ecx가 0에서 99까지 범위를 도는 카운트드 루프는 두 개의 매크로 명령 begin_loop와 end_loop로 표현할 수 있으며, 오른쪽처럼 확장된다:
; 원본 코드 ; 매크로 전개 후 코드
mov ecx, 0
begin_loop lbl, ecx, 0 lbl:
... ...
... ...
end_loop lbl, ecx, 100 inc ecx
cmp ecx, 100
jne lbl
begin_loop는 루프 인덱스 레지스터를 초기화하고 루프 본문의 첫 명령에 레이블을 연결한다. end_loop는 루프 인덱스를 증가시키고 인덱스의 최종 값에 도달하지 않았으면 루프 본문을 다시 실행한다.
순서도는 어셈블리어로 프로그래밍할 때 중요한 도구이다. 프로그램 순서도는 기본 계산 단계를 상자로, 제어 흐름을 화살표로 나타낸 프로그램(또는 프로그램 조각)의 그래픽 표현이다. 예를 들어, 별표 문자 다섯 개를 출력하는 카운트드 루프의 순서도는 다음과 같다:

1945년부터 1985년까지, 즉 최초의 현대 컴퓨터부터 구조적 프로그래밍의 광범위한 채택에 이르기까지, 순서도는 프로그램을 설계하고 문서화하는 데 선호되는 방식이었다. 순서도는 상자를 구현하는 정확한 명령 시퀀스, 코드에서의 발생 순서, 제어 흐름을 구현하는 데 사용되는 레이블과 분기 등 여러 세부사항을 생략한다. 이것이 순서도의 장점이다. 어셈블리 코드보다 더 높은 수준의, 덜 복잡한 관점을 제공한다. 그러나 단점도 있다. 순서도를 어셈블리 코드로 번역하는 데 상당한 수작업이 필요하며, 프로그램이 변경될 때 순서도와 코드를 동기화하기 어렵다.
식(Expressions).IBM이 1957년에 도입한 Fortran I는 대수 표기법으로 작성된 일반적인 식을 처음으로 지원한 프로그래밍 언어이다. (이름이 “FORmula TRANslator”를 의미하는 이유다.) 예를 들어, 이차 방정식 A x 2 + B x + C = 0의 해 X1과 X2는 Fortran으로 간결하게 표현된다:
D = SQRT(BB - 4AC) X1 = (-B + D) / (2A) X2 = (-B - D) / (2*A)
이러한 식을 기본 프로세서 명령으로 구현하고, 중간 결과를 보관하기 위해 레지스터나 임시 변수를 사용하는 것은 Fortran 컴파일러의 역할이다. 참고로 위 계산을 위한 AArch64 어셈블리 코드는 다음과 같다:
ldr d0, B fsub d0, d0, d1 fadd d3, d1, d0
fmul d0, d0, d0 fsqrt d0, d0 fdiv d3, d3, d2
ldr d1, A ldr d1, B str d3, X1
fmov d2, #4.0 fneg d1, d1 fsub d3, d1, d0
fmul d1, d1, d2 ldr d2, A fdiv d3, d3, d2
ldr d2, C fmov d3, #2.0 str d3, X2
fmul d1, d1, d2 fmul d2, d2, d3
DO 루프.Fortran I는 프로그래밍 언어에서 최초의 제어 구조를 도입한 것으로도 유명한데, 바로 DO 카운트드 루프다.
DO 100 I = 1, N ... ... 100 ...
이 코드는 DO 명령과 레이블 100(불포함) 사이의 줄들을 반복 실행하며, 변수 I는 1, 2, …, N의 연속 값을 갖는다. 루프 본문은 I를 변경하지 않아야 하지만, 일부 컴파일러는 이를 허용하기도 한다.
공통 프로그래밍 관용구를 간결하게 표기할 뿐 아니라, DO 표기법은 효율적인 기계 코드로 컴파일될 수 있다. 원래 Fortran I 컴파일러는 루프 검사를 루프 본문 끝에 배치했고, 제한적 형태의 루프 불변 코드 이동과 주소 지정 강도 축소를 수행했다. DO 루프에 잘 적용되는 다른 최적화에는 루프 언롤링과 벡터화가 있다.
Fortran의 제어.DO 루프 외에도 Fortran I는 레이블로 무조건 점프하는 GOTO 명령을 지원한다. 예를 들어, 무한 루프는 다음과 같이 쓴다:
100 ... ... GOTO 100
GOTO 명령은 또한 계산된 점프(computed jump)를 지원하는데, 이는 점프할 레이블의 값이 변수에 들어 있는 경우이다. 또한 ASSIGN 명령은 변수에 레이블의 값을 설정한다. 예를 들어:
ASSIGN 100 TO DEST ... GOTO DEST
Fortran I는 특이한 3방향 산술 분기 IF (X) L1, L2, L3를 통해 조건부 점프를 지원하는데, X< 0이면 L1, X = 0이면 L2, X> 0이면 L3로 분기한다. 예를 들어, 다음 코드 조각은 Y를 X의 절댓값으로 설정한다:
Y = X IF (X) 701, 702, 702 701 Y = -Y 702 ...
친숙한 불리언 조건을 갖춘 IF 명령은 Fortran IV(1961)에서 도입되었으며, 불리언 연산자와 불리언 비교 .EQ.(같음), .LT.(작음) 등도 함께 도입되었다. 예를 들어 X의 절댓값 계산은 다음과 같이 쓸 수 있다:
Y = X IF (X .LT. 0) Y = -Y
IF 뒤에는 하나의 명령만 올 수 있고(IF와 DO는 예외), ELSE 절은 없다. 더 복잡한 조건 명령은 IF…GOTO와 레이블을 사용해 표현해야 한다:
IF (X .LT. 0) GOTO 100 Y = X GOTO 101 100 Y = -X 101
Fortran 77은 블록 구조의 조건문 IF…ELSE…END IF를 추가했으므로, 앞의 예를 더 명확히 다음과 같이 쓸 수 있다
IF (X .LT. 0) THEN Y = -X ELSE Y = X END IF
Fortran 99는 블록 구조의 카운트드 루프 DO…END DO와 일반 루프 DO WHILE…END DO를 추가했다.
구조적 문장.Fortran 및 대부분의 다른 프로그래밍 언어에서 (1 + X) * Y 같은 식은 상수(여기서는 1)와 변수(X, Y)를 연산자(+ , *)로 결합하여 형성한다. 1960년에 소개된 Algol 프로그래밍 언어는 이 접근을 명령(“문장”이라고도 하며 아래에서는 s로 표기)으로 확장한다. 문장은 다음과 같은 기본 명령으로부터 만들어지며,
이러한 기본 명령들은 다시 다음과 같은 제어 구조를 사용해 결합될 수 있다.
begin s``1``; s``2``; … endif be then s``1``else s``2``for i := e``1``step e``2``until e``3``do swhile be do s여기서 e는 산술 식을, be는 불리언 식을 뜻한다. 블록 begin … end는 지역 변수와 지역 레이블을 선언할 수도 있다. 예를 들어, 다음 블록 문장은 배열 a[1:n]에서 가장 큰 값을 m에, 그 값의 위치를 p에 설정한다.
begin integer i; m := min_int; p := 0; for i := 1 step 1 until n do if a[i] >= m then begin p := i; m := a[i] end end
프로그래밍 언어에 대한 새로운 관점. 어셈블리어 명령이나 Fortran 명령과 달리, Algol 문장은 재귀적 성격을 가지며 임의의 깊이로 중첩될 수 있다. 이는 이러한 재귀적 성격을 수용하고 합성 가능성을 강조하는, 프로그래밍 언어를 서술하는 새로운 방식으로 이어졌다.
구문적 수준에서, Algol은 최초로 형식 문법(Backus–Naur Form)으로 정의된 프로그래밍 언어였다. 다음은 단순화된 Algol 유사 언어의 문법이다:
문장(Statements): s::=x:=e ∣proc(e 1, …, e n) ∣begin s 1; …; s n end ∣if be then s 1[else s 2] ∣while be do s ∣for x:=e 1 step e 2 until e 3 do s 산술 식(Arithmetic expressions): e::=c ∣ x ∣ e 1 + e 2 ∣ … 불리언 식(Boolean expressions): be::=true ∣ false ∣ e 1<e 2 ∣ …
그 결과, Algol 컴파일러는 재귀적 또는 스택 기반 파싱 알고리즘을 최초로 사용했다.
의미적 수준에서, Algol은 한 명령의 의미가 그 하위 명령들의 의미의 조합으로 결정된다는 생각을 대중화했다. 이 생각은 처음에는 비형식적이었으나, 이후 Strachey, Scott 등을 중심으로 한 표기 의미론(denotational semantics)의 작업에서 수학적으로 정밀해졌다(섹션6.2 참조).
비구조적 제어.제어 구조 외에도, Algol은 문장에 부여된 레이블 L로 점프(goto)하는 기능을 지원한다:
문장(Statements): s::=… ∣L: s 문장 L에 레이블을 붙임 ∣goto L ∣ go to L ∣ go L 레이블 L로 점프
1963년 개정 보고서에서는 goto 철자를 사용하고, 1976년 보고서에서는 go to를 사용하며, 많은 컴파일러가 go도 받아들인다.
각 레이블은 자신의 유효 범위(scope)를 가지며, 이는 이 레이블의 정의를 둘러싼 가장 가까운 블록이다. goto 문은 현재 유효 범위에 있는 레이블(즉, goto 문과 같은 블록이나 더 바깥 블록에서 정의된 레이블)로만 점프할 수 있다. 결과적으로, goto는 블록 바깥에서 블록 안으로 점프할 수 없다.
블록 내부로 점프✔
begin integer i; L:... goto L end 블록 밖으로 점프✔
begin integer i; ... goto L ... end; L: ...블록 안으로 점프✘
goto L; begin integer i; L:... end
Algol 스타일로 제어 구조와 goto 점프를 결합하는 방식은 교과서와 저널에서 알고리즘을 서술하는 선호 방식이 빠르게 되었다. 1960년경부터 컴퓨터 과학 초기에 중요한 역할을 한 학술지 Communications of the ACM은 새로운 알고리즘을 게재할 때 순서도 대신 Algol 사용을 의무화했다.
이러한 Algol 스타일의 예로, 정렬된 배열 a[1:n]에서의 이진 탐색을 보이자.
begin integer low, high, mid; low := 1; high := n; while low <= high do begin mid := low + (high - low) / 2; if x < a[mid] then high := mid - 1 else if x > a[mid] then low := mid + 1 else goto FOUND end; NOTFOUND: ... FOUND: ... end
우리는 많은 명령형 및 객체지향 언어에서 제어 구조와 goto의 조합을 보게 된다: Algol 68, Pascal, Ada, Simula, PL/I, C, C++, C#, Perl, Go,… 반면 goto 없이 구조적 제어만 제공하는 명령형 언어는 비교적 적다: Modula-2(1980), Eiffel(1986), Modula-3(1988), Python(1991), Java(1995), Rust(2012), Swift(2014).
Algol 60이 도입한 제어 구조(블록, 조건문, 카운트드 루프, 일반 루프)는 이후 대부분의(거의 모든) 명령형 언어에서 찾아볼 수 있다. 이러한 제어 구조의 다양한 변형과 확장이 제안되어 일부 언어에 도입되었다.
조건문의 다양한 풍미.많은 언어가 중첩된 if…then…else 조건문 작성을 돕기 위해 elif 또는 elseif 구문을 제공한다:
if be 1 then s 1 elif be 2 then s 2 elif … else s n
연쇄적인 불리언 검사를 위한 대체 문법으로 Lisp의 cond 구문이 있다:
(cond (be 1 s 1) (be 2 s 2) … (t s n))
정수형이나 열거형 값에 대한 분기 분석(case 분석)은 Pascal의 case 구문(왼쪽), C와 C 유사 언어의 switch 구문(가운데), 그리고 Python의 match 구문(오른쪽)으로 제공된다:
case grade of'A' : s 1'B', 'C': s 2'D' : s 3'F' : s 4 end switch (grade) { case'A': s 1; break; case'B': case'C': s 2; break; case'D': s 3; break; case'F': s 4; break; }match grade: case 'A' : s 1 case 'B' | 'C': s 2 case 'D' : s 3 case 'F' : s 4
패턴 매칭은 대부분의 함수형 언어(Haskell, OCaml, SML, …)와 일부 명령형 언어(Rust, Swift, C#, …)에서 찾아볼 수 있으며, 단순한 case 분석을 넘어 데이터 타입을 구조 분해하고 값들을 변수에 바인딩하는 능력을 제공한다.
Dijkstra의 Guarded Command Language(프로그램 명세와 정련을 위한 표기법)는 해당 가드 be 1, …, be n 중 참인 것들에 대응하는 명령 s 1, …, s n 중 하나를 비결정적으로 실행하는 선택 구문을 가진다:
if be 1→s 1 ⫿be 2→s 2 ⋮⋮ ⫿be n→s n fi
여러 가드가 참이면, 그에 대응하는 명령 중 아무거나 실행될 수 있다. 어떤 가드도 참이 아니면 프로그램은 중단(abort)한다. 유사한 구문이 통신 기반 채널을 기술하는 Occam 언어에 사용된다.
반복문의 다양한 풍미.while 루프는 각 반복의 시작에서 루프 조건을 검사한다. 따라서 조건이 초기에 거짓이면 루프 본문은 전혀 실행되지 않을 수 있다. 대안으로 각 반복의 끝에서 조건을 검사하여, 루프 본문이 최소 한 번은 항상 실행되도록 할 수도 있다. 이는 C 유사 언어에서는 do…while, Pascal에서는 repeat…until로 쓴다. 다음 프로그램 등가가 성립한다:
do s while be ≈ s; while be do s repeat s until be ≈ s; while not be do s while be do s ≈ if be then do s while be ≈ if be then repeat s until not be
repeat…until은 루프 조건의 의미를 “참이면 계속”에서 “참이면 중지”로 뒤집는다는 점에 유의하라.
때로는 루프 본문의 중간에서 조건을 검사하는 것이 유용한데, 이 경우 본문의 일부는 항상 조건 검사 전에 실행된다. 이 관용구는 “한 번 반(loop and a half)”으로 알려져 있다. Forth 언어에는 이 패턴을 표현하는 BEGIN…WHILE…REPEAT 구문이 있다. Ada에서는 다음과 같이 쓸 수 있다
loop s 1; exit when be; s 2 end loop
이는 s 1에 exit 문이 없다는 전제하에, s``1``; while be do begin s``2``; s``1``end와 동치이다. 다른 언어들은 이 목적을 위해 조기 종료가 있는 무조건 루프를 사용한다(섹션1.6 참조).
무조건 루프는 루프 본문이 종료를 유발할 때만 멈추며, 일부 언어는 이를 위한 특별한 구문을 제공한다: Ada의 loop…end loop, Go의 for {…}. 다른 언어들은 while true do나 repeat…until false 같은 자명한 조건을 가진 루프를 사용한다.
PL/I와 Algol 68에서는 카운트드 for 루프가 추가적인 while 조건을 가질 수 있다. Algol 68에는 무조건 루프, while 루프, for 루프를 모두 포괄하는 매우 일반적인 do 루프가 있다:
[FOR index ] [FROM first ] [BY increment ] [TO last ][WHILE condition] DO statements OD
대괄호로 표시된 절은 일부 또는 전부 생략될 수 있다.
더 현대적인 언어들은 숫자 범위뿐 아니라 배열, 리스트, 제네레이터(섹션4.2 참조) 같은 값의 컬렉션 전체를 순회하는 for 루프를 제공한다. 예를 들어 Python에서는 다음과 같이 쓴다
for item in collection: s
Java에서는 다음과 같이 쓴다
for (Type item: collection) { s }
Algol 또는 Algol 스타일의 의사코드로 알고리즘을 작성한 경험에 따르면, goto 문 사용의 많은 부분은 while이나 for 루프의 조건이 거짓이 되거나 루프 인덱스가 최종 값에 도달하기 전에 실행을 조기에 종료하기 위해서이다(이는 페이지??의 이진 탐색 예에서 그러하다).
많은 프로그래밍 언어는 둘러싼 루프에서 빠져나와 루프 뒤의 프로그램 지점으로 점프하는 명령을 제공하여, 루프를 즉시 종료할 수 있게 한다. 이 명령은 C, C++, Java, Python에서는 break, Ada와 Fortran 90에서는 exit, Perl에서는 last, Forth에서는 leave로 쓴다. 예를 들어, 다음은 음수가 입력될 때까지 대화형으로 입력한 수를 합하는 Java 루프이다:
while (true) { number = input.nextNumber(); // number가 음수면 루프 종료 if (number < 0) break; sum += number; }
이들 언어 중 일부는 둘러싼 루프의 현재 반복을 중지하고 즉시 다음 반복을 시작하는 또 다른 명령도 제공한다. 이는 C, C++, Java, Python에서는 continue, Fortran 90에서는 cycle, Perl에서는 next, Forth에서는 again으로 쓴다.
Python은 루프가 정상적으로 종료되었는지, break 명령 실행으로 종료되었는지를 구분하는 방법을 제공한다. else 절은 루프가 정상 종료된 경우에만 실행되며, break가 실행되면 건너뛴다. 예를 들어:
for n in numbers: if n < 0: print("Negative number found"); break else: print("No negative number found")
때때로 즉시 둘러싼 루프가 아니라, 더 바깥의 둘러싼 루프를 종료하고 싶을 때가 있다. 본 셸(Bourne shell)에서는 종료할 루프를 상대적 위치로 식별한다: break N은 N개의 둘러싼 루프를 종료한다. Ada, Java, Rust에서는 루프를 레이블로 식별한다. 각 루프는 선택적으로 레이블을 가질 수 있으며, break L은 L로 레이블된 루프까지(포함하여) 모든 루프를 종료한다.
예를 들어, 다음 Java 코드는 양의 원소를 행렬에서 찾는다:
elt = 0; search: for (int x = 0; x < dimx; x++) for (int y = 0; y < dimy; y++) { elt = mat[x][y]; if (elt > 0) break search; }
마찬가지로, 다음 Java 코드는 길이 m의 하위 문자열 needle이 길이 n의 문자열 haystack에 존재하는지 찾는다:
search: for (int i = 0; i <= n - m; i++) { for (int j = 0; j < m; j++) if (haystack.charAt(i + j) != needle.charAt(j)) continue search; return true; // 찾음 } return false; // 찾지 못함
이러한 다중 레벨 종료(multi-level exits)는 구조적 제어의 표현력을 연구할 때 중요한 역할을 하며, 섹션2.4와2.6에서 보인다.
개념적으로, 그리고 때로는 실제로도, 루프뿐만 아니라 하나 이상의 둘러싼 블록에서 빠져나와 그 블록들에서 종료 지점을 따른 문장들을 건너뛰는 것이 유용하다. Rust는 프로그래머가 블록에 레이블 L을 설정하고, break L로 이 블록에서 빠져나오게 허용한다. C와 C++에서는 블록을 do … while(0) 형태의 퇴화된 루프로 쓸 수 있지만, break는 한 레벨만 지원된다.
WebAssembly는 루프와 블록을 통합적으로 다루며, 이를 “구조적 제어 명령(structured control instructions)”이라고 부른다. br N 명령은 둘러싼 구조적 제어 명령들 중 (N+1)번째의 끝으로 점프하는데, 그것이 블록이면 끝으로, 루프면 시작으로 점프한다. 이 관용구는 다중 레벨 break와 다중 레벨 continue를 모두 지원한다.
Pratt and Zelkowitz (2000)의 교재 Programming Languages: Design and Implementation은 제어 구조와 자료 구조 관점에서 12개의 프로그래밍 언어를 비교한다. 이 책은 또한 본서 전반에서 사용하는 프로그래밍 언어 개념에 대한 훌륭한 입문서이기도 하다.
Carpenter and Doran (1977)은 튜링의 ACE 명령 집합 아키텍처와 그것이 폰 노이만의 EDVAC 아키텍처와 어떻게 다른지를 설명한다.
Backus (1998)는 그와 IBM 팀이 Fortran I, II, III를 어떻게 설계했는지를 들려준다. Padua (2000)는 “고급 언어로부터 효율적인 기계 코드를 자동 생성하는 것이 가능함을 최초로 입증”한 Fortran I 컴파일러를 설명한다.
Algol 60은 큰 사용자층을 갖지는 못했지만, 오늘날 우리가 사용하는 프로그래밍 언어에 엄청난 영향을 끼쳤다. Speed (2000)은 유머러스하게도 이를 “당신이 한 번도 써보지 않았을 가장 위대한 컴퓨터 언어이자 프로그래밍 가계도의 시조(grandaddy)”라고 쓴다. Rutishauser (1967) (온라인으로 볼 수 있음)은 역사적 주석과 함께 Algol 60에 대한 포괄적 설명을 제공한다.