요약
이 수업은 강좌의 여덟 번째이자 마지막 단원, 곧 더 큰 프로그램을 다루는 단원을 연다. 단원의 목적을 설명한다. 이제 한 파일에도, 머릿속에도 들어가지 않는 프로그램을 구성하고, 검증하고, 디버깅하는 것이다. 단원의 세 수업과 그것들을 잇는 실을 소개하는데, 그 실은 강좌의 첫 수업에서 열린 고리(명세하기, 만들기, 검증하기)를 닫는다. 단원이 이미 안다고 전제하는 것, 곧 강좌 전체를 되짚는다. 단원이 해결하는 문제를 제시한다. 한 파일로 된 프로그램이 테스트에 쓴 데이터로는 잘 동작하면서 자기 명세의 경계에 있는 오류에 대해서는 침묵하는 문제이다. 테스트하는 것과 증명하는 것의 차이를 미리 소개한다. 그리고 단원이 최종 프로젝트와 C++ 프로그래밍 II 강좌에서 어디로 이어지는지 보여 준다.
학습 목표
- 단원의 내용 순서를 설명하고, 각 수업이 뒤이은 수업에 무엇을 보태는지 설명한다.
- 예를 들어, 함수의 선언과 정의의 차이, 함수를 규정하는 계약, 그리고 여러 파일을 하나의 프로그램으로 바꾸는 단계(컴파일과 링크)를 떠올린다.
- 예를 들어, 몇 개의 케이스로 프로그램을 테스트하는 것과 허용되는 모든 입력에 대해 프로그램이 올바르다고 증명하는 것의 차이를 미리 안다.
- 단원과 강좌를 마쳤을 때 할 줄 알아야 하는 것이 무엇인지 알고, 그것을 C++ 프로그래밍 II 강좌에서 배울 것과 구별한다.
입문 평가
단원을 시작하기 전에 출발점을 재어 두면 좋다. 이 평가는 점수를 매기지 않으며 학습의 방향을 잡아 준다. 처음 일곱 문항은 단원이 이미 안다고 전제하는 것(선언과 정의, 사전 조건, 6단원에 나온 함수의 계약, 링커의 메시지, 이진 탐색의 케이스, 추적표, 벡터 밖의 읽기)을 확인한다. 그 뒤의 세 문항은 수업 순서대로 단원의 핵심 개념을 다루며, 거기서는 “아직 모르겠다”가 정직한 답이다. 모든 답은 요점을 설명하고 그 요점을 다루는 수업을 알려 준다. 같은 능력은 종합 수업에서 다시 측정한다.
단원을 시작하기 전에 출발점을 확인해 보세요. 짧은 자기 평가입니다. 점수가 매겨지지 않으며 결과는 브라우저에만 저장됩니다.
문제 1이전 단원에서
main함수 앞에 쓴int gcd(int a, int b);줄은 무엇을 하는가?선언과 정의를 혼동한다. 본체가 없으면 실행할 것이 없고, 어떤 함수도 쓰여 있다는 이유로 실행되지 않으며 호출될 때 실행된다.선언과 호출을 혼동한다. 호출에는 매개변수의 자료형이 붙지 않는다.컴파일러가 호출을 검사하는 데는 시그니처로 충분하다는 것을 모른다. 본체가 없다는 사실은, 아무도 본체를 쓰지 않으면, 컴파일러가 아니라 링커가 발견한다.선언만 있으면 컴파일러는 호출을 하나하나 검사할 수 있다. 본체를 갖춘 정의는 더 아래에 있어도 되고 다른 파일에 있어도 된다. 여러 파일과 자체 라이브러리 수업 전체가 이 분리에 기대고 있다. 헤더는 선언하고, 구현 파일은 정의한다.복습: 함수
문제 2이전 단원에서
toBase(n, base)함수의 사전 조건은 “n >= 0조건과2 <= base <= 10조건”이다. 누가 그것을 보장해야 하는가?사전 조건을 입력 검증과 혼동한다. 검증은 프로그램 경계의 일이지, 각 함수의 일이 아니다.주석이 검사된다고 믿는다. 컴파일러는 주석을 버린다.계약이 의무를 나눈다는 것을 보지 못한다. 계약이 없으면 무언가 잘못되었을 때 누가 틀렸는지 아무도 모른다.문제 3이전 단원에서6.4강의
binarySearch(values, target)함수는target값의 위치 하나를 돌려주고, 그 값이 없으면values.size()값을 돌려준다. 이 함수의 사전 조건은 무엇인가?벡터가 비어 있으면 반복문은 한 회차도 돌지 않고 함수는 0, 곧values.size()값을 돌려준다. 이 경우는 사후 조건이 다루고 있다.함수는 값이 없음을 알려 준다. 값이 있기를 요구하면 함수는 쓸모가 없어진다.중복이 있으면 함수는 그 값의 위치 가운데 하나를 돌려주며, 사후 조건은 이를 허용한다.각 회차는 가운데 원소와 비교하여 구간의 절반을 버리는데, 이는 벡터가 정렬되어 있을 때만 옳다. 정렬되지 않은 데이터에서는 함수가 아무 경고 없이 틀린 답을 낸다. 사전 조건은 계약에 적고, 호출하는 쪽은 거기서 그것을 본다.복습: 탐색과 정렬
문제 4이전 단원에서어떤 프로그램이 오류 없이 컴파일되는데, 실행 파일을 빌드하면
undefined reference to `tetrahedral(int)'메시지가 나온다. 어느 단계가 실패했는가?파일과 줄을 가리키는 컴파일러의 메시지와, 오브젝트 파일과 이름을 가리키는 링커의 메시지를 구별하지 못한다.프로그램은 아직 생겨나지도 않았다. 실행 파일이 없으면 실행도 없다.#include지시문이 빠지면 컴파일할 때was not declared in this scope메시지가 나오지, 이 메시지가 나오지 않는다.컴파일러는 선언을 보았기 때문에 모든 호출을 받아들였다. 링커는 모든 오브젝트 파일과 라이브러리에서 정의를 찾았지만 찾지 못했다. 여러 파일과 자체 라이브러리 수업이 그 흔한 원인을 설명한다. 컴파일되지 않은 파일, 또는 다른 자료형으로 베껴 쓴 선언이다.복습: 프로그램, 컴파일러, C++
문제 5이전 단원에서아래 케이스 묶음 가운데 이진 탐색을 가장 잘 시험하는 것은 어느 것인가?
양과 포괄 범위를 혼동한다. 값이 없는 경우도 경계도 결코 시험하지 않는데, 오류는 대개 거기에 있다.이미 통과한 케이스는 새로운 것을 발견할 가능성이 가장 낮은 케이스이다.그것은 무한히 많거나, 자료형의 범위 안에서도 천문학적으로 많다. 테스트는 고르는 일을 요구한다.문제 6이전 단원에서
int a{0}; int b{1}; for (int i{0}; i < 4; ++i) { const int t{a + b}; a = b; b = t; }코드를 실행한 뒤a변수와b변수의 값은 얼마인가?한 회차를 더 센다.i < 4조건에서는 본체가 다섯 번이 아니라 네 번 실행된다.한 회차를 덜 센다. 반대 방향의 하나 차이 오류이다.t변수가a변수가 바뀌기 전에 계산한 합을 간직한다는 것을 잊는다. 마지막 회차에서a변수는b변수의 3을 받지만,b변수가 받는 것은 그 합인 5이지a변수의 새 값이 아니다.문제 7이전 단원에서
std::vector<int> v{1, 2}; std::println("{}", v[2]);코드는 왜 강좌의 옵션으로는 멈추는데, 새니타이저 없이-O2옵션만으로 컴파일하면 아무 수나 출력할 수 있는가?최적화가 오류를 고친다고 믿는다. 최적화는 검사를 없애고 프로그램이 표준을 어기지 않는다고 가정할 뿐이다.효과를 거꾸로 본다. 프로그램을 멈추는 것은 최적화하지 않았을 때의 라이브러리 검사이다.인덱스의 하나 차이 오류이다. 원소가 두 개인 벡터의 인덱스는 0과 1이다.문제 8인클루드 가드를 갖춘 헤더가
inline키워드 없이double scale(double x)함수를 (선언만 하는 것이 아니라) 정의하고,main.cpp파일과report.cpp파일이 그 헤더를 포함한다. 프로그램을 빌드하면 어떻게 되는가?가드가 파일들 사이에서 작동한다고 믿는다. 가드는 각 번역 단위 안에서 작동한다.컴파일되는 파일과 프로그램 전체를 혼동한다. 각 파일 안에는 정의가 하나뿐이다.정직한 답이다. 8.1강에서 링커의 실제 메시지로 그것을 보여 준다.가드는 한 파일에 헤더가 두 번 들어가는 것을 막지만, 각.cpp파일은 따로 컴파일되어 정의의 사본을 하나씩 받는다. 링커는 정의를 두 개 찾게 된다. 이것이 여러 파일과 자체 라이브러리 수업의 단일 정의 규칙이다.복습: 여러 파일과 자체 라이브러리
문제 9어떤 함수가 신중하게 고른 자동 테스트 오백 개를 통과한다. 무엇을 주장할 수 있는가?
많음과 증명을 혼동한다. 어떤 케이스도 건드리지 않은 경계의 오류는 안쪽 케이스가 아무리 많아도 살아남는다.케이스는 가능한 오류들 가운데서 무작위로 뽑은 표본이 아니며, 통과한 빈도는 프로그램이 올바를 확률을 재지 못한다.정직한 답이다. 8.2강은 이 물음에서 시작한다.문제 10반복문 안의
total += k;줄에 중단점이 있고, 프로그램이 그곳에서 세 번째로 멈춘다. 강조 표시된 줄은 무엇을 나타내는가?멈춤을 이미 내디딘 걸음과 혼동한다.프로그램이 멈추는 곳(디버깅하는 사람이 고르는 곳)을 장애의 원인과 혼동한다.정직한 답이다. 8.3강에서 중단점과 단계별 실행을 다룬다.
콘텐츠를 불러오는 중…