현실의 프로그램은 굉장히 긴 코드들로 구성되어있다.(여기에서 다루는 예제들과 달리) C가 큰 프로그램 작성을 위해 설계되지는 않았지만 불가능한건 아니며, 실제로도 그렇다. 여기서는 규모가 큰 프로그램을 작성할때 유요한 C의 특징을 다룰것이다.
규모가 있는 프로그램을 짜는 것은 계산기 프로그램을 만드는 것과 차원이 다르다. 굉장히 많은 사람들이 참여하고, 수정도 빈번하기 때문에, 스타일, 문서화 등이 굉장히 중요해진다.
C 프로그램을 설계할 때, 독립적인 모듈 관점에서 살펴보는 것이 유용하다. 모듈은 서비스의 집합이며, 이들중 몇몇은 프로그램의 다른 부분에서도 기능한다. 각 모듈은 가능한 서비스를 나타내는 인터페이스 를 가지며, 모듈에 관한 세부사항은 모듈의 구현(implementation) 에 담겨있다.
C의 관점에서 서비스는 곧 함수다. 모듈의 인터페이스는 함수 프로토타입을 포함한 헤더파일이다. 모듈의 구현은 모듈 함수의 정의를 포함한 소스파일이다.
즉 각 모듈의 헤더파일은 인터페이스, 소스파일은 구현에 대응하며, main 함수는 모듈의 클라이언트(client)다. C 라이브러리는 이러한 모듈의 집합(collection)이다.
프로그램을 모듈로 나누는 것은 여러 이점을 우리에게 가져다준다.
추상화: 팀원들이 모듈의 작동 방식에 대해 일단 합의를 하게되면 더이상 모듈이 정확히 어떻게 작동하는지 이해할 필요가 없어진다. 팀 작업을 함에 있어 각 구성원들은 독립적으로 작업을 수행할 수 있다.
재활용: 한 번 잘 만든 모듈은 다른 프로그램에서도 그대로/혹은 조금의 수정을 거쳐 사용할 수 있다.
유지보수: 버그 발생 지점을 특정하거나, 기능 수정에 있어 굉장히 편리해진다. 왜냐하면 해당 모듈만 다시 컴파일하면 되기 때문이다.
위의 세 가지가 전부 중요하지만, 유지보수를 위해서라도 모듈 방식의 프로그래밍은 반드시 필요하다. 타이어를 교체할때 자동차를 오버홀 할 수는 없지 않은가?
무분별하게 선언을 하는 것은 좋은 인터페이스가 아니다. 다음 두 가지 속성을 가져야한다.
높은 응집력: 모듈 내의 원소는 서로 밀접한 관련이 있어야한다. 이를 통해 프로그램 전체를 쉽게 이해하고 사용할 수 있다.
낮은 연결성: 각 모듈은 되도록 서로 독립적이어야 한다. 그러면 모듈을 수정하고 재사용하기 용이해진다.
높은 응집력과 낮은 연결성을 위해 모듈은 다음과 같은 유형을 가지게 되었다.
데이터 풀(data pool): 연관을 갖는 변수 혹은 상수들의 집합. 설계관점에서 헤더 파일에 변수를 넣는 것은 일반적으로 바람직하지 않으나, 연관 있는 상수들을 모아두는 것은 유용하다.
라이브러리(library): 관련있는 함수들의 집합
추상-객체(abstract object): 일반적으로 C에선, 객체란 값이 저장될 수 있는 메모리 블럭을 나타낸다. 하지만 이번 장에서는 데이터와 이들에 대한 연산자를 의미한다. 모듈은 숨겨진 데이터 구조에 대한 연산을 수행하는 함수의 집합이 될 수 있다.
추상 자료형(abstract data type): ADT는 abstract object의 자료형 버전이라고 생각하면된다.
잘설계된 모듈은 클라이언트로부터 정보를 은닉한다. 여기에는 주로 두가지 이점이 있다.
보안: 데이터 구조에 대한 이해가 부족한 사용자가 임의 사용을 통해 오염을 일으킬 가능성을 줄인다.
유연성 모듈 내부 작동방식을 수정하는 것이 어렵지 않다.
C에서 정보 은닉을 강제하는 도구가 바로 static 저장 클래스다.
바로 앞에서 다뤘던, 추상-객체 모듈은 심각한 단점을 가진다. 바로 객체의 복수 인스턴스를 가질 방법이 없다는 것이다. 이를 위해 우리는 새로운 자료형을 만들어야한다.
안타깝게도 C에서는 사용자가 추상화 되지 않은 자료형을 조작하는 것을 막는 방법이 제한된다.
C가 캡슐화를 위해 지원하는 것은 미완성형 뿐이다. C 표준은 미완성형을 "객체를 나타내지만, 크기를 결정하는 정보가 결여되어있다."고 말한다. 예를 들어
struct t;
는 컴파일러에게 t가 구조체임을 알려주지만, 멤버에 대해선 말하지 않는다. 그 결과, 컴파일러는 구조체의 크기를 결정할 정보가 부족하다.
자료형이 미완성에 남아있을수록, 그 사용이 제한된다. 왜냐하면 컴파일러가 미완성형의 크기를 모르기 때문이다. 변수 선언에도 사용할 수 없다. 하지만 포인터 형을 정의할 때에는 사용할 수 있다.
typedef struct t *T;
여기서 우리가 할 수 없는 것은 -> 연산자를 사용할 수 없다는 것이다.(구성원에 대한 정보가 없으므로)