일부 C 프로그램이 하나의 거대한 소스 파일로 작성된 이유는 무엇입니까?


88

예를 들어, 과거 의 SysInternals 도구 "FileMon"에는 소스 코드가 완전히 4,000 행 파일 인 커널 모드 드라이버가 있습니다. 최초의 핑 프로그램 (~ 2,000 LOC)도 마찬가지입니다.

답변:


143

여러 파일을 사용하려면 항상 추가 관리 오버 헤드가 필요합니다. 컴파일 및 링크 단계가 분리 된 빌드 스크립트 및 / 또는 makefile을 설정하고, 서로 다른 파일 간의 종속성이 올바르게 관리되는지 확인하고, 이메일 또는 다운로드를 통해 소스 코드를보다 쉽게 ​​배포 할 수 있도록 "zip"스크립트를 작성해야합니다. 의 위에. 오늘날의 IDE는 일반적으로 많은 부담을 겪지 만, 첫 번째 핑 프로그램이 작성 될 당시에는 그러한 IDE를 사용할 수 없었습니다. 그리고 ~ 4000 LOC 정도로 작은 파일의 경우 여러 파일을 잘 관리하는 IDE가 없으면 언급 된 오버 헤드와 여러 파일 사용의 이점 사이의 균형을 통해 사람들이 단일 파일 접근 방식을 결정할 수 있습니다.


9
"~ 4000 LOC 정도로 작은 파일의 경우 ..."지금 JS 개발자로 일하고 있습니다. 파일 길이가 400 줄에 불과한 파일이 있으면 크기가 얼마나 커질 까 걱정됩니다! (그러나 우리 프로젝트에는 수십, 수십 개의 파일이 있습니다.)
Kevin

36
@Kevin : 머리에 머리카락 하나가 너무 적고 수프에 머리카락 하나가 너무 많습니다 ;-) JS의 AFAIK 여러 파일은 "현대 IDE가없는 C"와 같이 많은 관리 오버 헤드를 유발하지 않습니다.
Doc Brown

4
@Kevin JS는 상당히 다른 짐승입니다. JS는 사용자가 웹 사이트를로드 할 때마다 최종 사용자에게 전송되며 브라우저에서 웹 사이트를 캐시하지 않습니다. C는 코드를 한 번만 전송하면 다른 쪽 끝에있는 사람이 코드를 컴파일하고 컴파일 된 상태로 유지됩니다 (예외는 있지만 일반적으로 예상되는 유스 케이스입니다). 또한 C 항목은 사람들이 주석에서 설명하는 '4000 줄은 정상입니다'프로젝트의 대부분과 마찬가지로 레거시 코드 인 경향이 있습니다.
Pharap

5
@Kevin 이제 가서 underscore.js (1700 loc, 하나의 파일)와 배포 된 수많은 다른 라이브러리가 어떻게 작성되는지 봅시다. Javascript는 실제로 모듈화 및 배포와 관련하여 C만큼 나쁩니다.
Voo

2
@Pharap 코드를 배포하기 전에 Webpack 과 같은 것을 사용해야한다고 생각 합니다. Webpack을 사용하면 여러 파일을 작업 한 다음 하나의 번들로 컴파일 할 수 있습니다.
Brian McCutchon

81

C는 모듈화에 좋지 않기 때문입니다. 더러워지고 (헤더 파일 및 #include, extern 함수, 링크 타임 오류 등) 더 많은 모듈을 가져올수록 더 까다로워집니다.

더 현대적인 언어는 C의 실수로부터 배웠기 때문에 부분적으로 더 나은 모듈화 기능을 가지고 있으며 코드베이스를 더 작고 간단한 단위로 쉽게 분류 할 수 있습니다. 그러나 C를 사용하면 너무 많은 코드로 간주되는 것을 단일 파일에 집중시키는 것을 의미하더라도 모든 문제를 피하거나 최소화하는 것이 좋습니다.


38
C 접근법을 '실수'로 묘사하는 것은 불공평하다고 생각합니다. 그들은 당시에 완벽하게 합리적이고 합리적인 결정을 내 렸습니다.
Jack Aidley

14
그 모듈화 요소 중 어느 것도 특히 복잡하지 않습니다. 잘못된 코딩 스타일 인해 복잡해질 수 있지만 이해하거나 구현하기가 어렵지 않으며 "실수"로 분류 할 수 없습니다. Snowman의 답변에 따라 실제 이유는 여러 소스 파일에 대한 최적화가 과거에는 그리 좋지 않았고 FileMon 드라이버에는 고성능이 필요하기 때문입니다. 또한 OP의 의견과 달리, 특히 큰 파일은 아닙니다.
Graham

8
@Graham 1000 줄 이상의 코드는 코드 냄새로 취급해야합니다.
메이슨 휠러

11
그되지 불공정 @JackAidley 전혀 갖는 뭔가 실수가이 시점에서 합리적인 결정이었다 말하는 배타적 상호 아닙니다합니다. 불완전한 정보와 제한된 시간을 감안할 때 실수는 피할 수 없으며 얼굴을 구하기 위해 수치스럽게 숨기거나 재 분류하지 않아야합니다.
Jared Smith

8
C의 접근 방식이 실수가 아니라고 주장하는 사람은 겉보기 열 라이너 C 파일이 실제로 모든 헤더 #include : d를 가진 천 개의 라이너 파일이 될 수있는 방법을 이해하지 못합니다. 즉, "wc -l"로 줄 수에 관계없이 프로젝트의 모든 단일 파일은 사실상 10,000 행 이상입니다. 모듈성을 더 잘 지원하면 구문 분석 및 컴파일 시간을 작은 부분으로 쉽게 줄일 수 있습니다.
juhist

37

역사적 이유 외에도 최신 성능에 민감한 소프트웨어에서이를 사용해야하는 이유가 있습니다. 모든 코드가 하나의 컴파일 단위에 있으면 컴파일러는 전체 프로그램 최적화를 수행 할 수 있습니다. 별도의 컴파일 단위를 사용하면 컴파일러는 특정 방식 (예 : 특정 코드 인라인)으로 전체 프로그램을 최적화 할 수 없습니다.

링커는 컴파일러가 수행 할 수있는 작업 외에도 일부 최적화를 수행 할 수 있지만 전부는 아닙니다. 예를 들어, 최신 링커는 여러 객체 파일에서도 참조되지 않은 기능을 효과적으로 제거합니다. 그들은 다른 최적화를 수행 할 수 있지만 컴파일러가 함수 내에서 할 수있는 것과는 다릅니다.

단일 소스 코드 모듈의 잘 알려진 예는 SQLite입니다. 자세한 내용 은 SQLite Amalgamation 페이지 에서 확인할 수 있습니다 .

1. 요약

100 개가 넘는 별도의 소스 파일이 "sqlite3.c"라는 하나의 큰 C 코드 파일로 연결되고 "아말감 화"라고합니다. 통합에는 응용 프로그램에 SQLite를 포함시키는 데 필요한 모든 것이 포함됩니다. 합병 파일의 길이는 180,000 줄 이상이고 6MB 이상입니다.

SQLite의 모든 코드를 하나의 큰 파일로 결합하면 SQLite를보다 쉽게 ​​배포 할 수 있습니다. 추적 할 파일은 하나뿐입니다. 또한 모든 코드가 단일 변환 단위로되어 있기 때문에 컴파일러는보다 나은 절차 간 최적화를 수행하여 기계 코드가 5 %에서 10 % 더 빠릅니다.


15
그러나 최신 C 컴파일러는 여러 소스 파일의 전체 프로그램 최적화를 수행 할 수 있습니다 (먼저 개별 객체 파일로 컴파일하는 경우는 아님).
Davislor

10
@Davislor 전형적인 빌드 스크립트를 보자 : 컴파일러는 현실적으로 그렇게하지 않을 것이다.

4
$(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(CFILES)모든 것을 단일 soudce 파일로 옮기는 것보다 빌드 스크립트를 변경하는 것이 훨씬 쉽습니다 . 프로덕션 대상에 대한 프로파일 링 및 디버깅을 해제하는 방법과 유사하게 변경되지 않은 소스 파일을 다시 컴파일하는 기존 빌드 스크립트의 대체 대상으로 전체 프로그램 컴파일을 수행 할 수도 있습니다. 모든 것이 하나의 큰 힙 소스에있는 경우 해당 옵션이 없습니다. 사람들이 익숙하지 않은 것은 아니지만 그것에 대해 번거로운 것은 없습니다.
Davislor

9
@Davislor 전체 프로그램 최적화 / 링크 타임 최적화 (LTO)는 코드를 개별 객체 파일로 "컴파일"할 때도 작동합니다 ( "컴파일"의 의미에 따라 다름). 예를 들어 GCC의 LTO는 컴파일 타임에 구문 분석 된 코드 표현을 개별 객체 파일에 추가하고 링크 타임에 (또한 존재하는) 객체 코드 대신이 코드를 사용하여 전체 프로그램을 다시 컴파일하고 빌드합니다. 따라서 초기 컴파일에서 생성 된 머신 코드는 무시되지만 개별 객체 파일로 먼저 컴파일되는 빌드 설정에서 작동합니다.
Dreamer

8
JsonCpp도 요즘도이 작업을 수행합니다. 핵심은 개발 중에 파일이 이런 방식이 아니라는 것입니다.
궤도에서 가벼움 레이스

15

다른 응답자가 언급 한 단순성 요소 외에도 많은 C 프로그램이 한 개인이 작성합니다.

개인 팀이있는 경우 코드 변경시 충돌이 발생하지 않도록 응용 프로그램을 여러 소스 파일로 분할하는 것이 바람직합니다. 특히 고급 프로그래머와 초보 프로그래머가 프로젝트에 참여하는 경우.

한 사람이 혼자서 일할 때는 문제가되지 않습니다.

개인적으로, 나는 기능에 따라 여러 파일을 습관적으로 사용합니다. 그러나 그것은 단지 나입니다.


4
@OskarSkog 그러나 미래의 자기 자신과 동시에 파일을 수정하지는 않습니다.
Loren Pechtel

2

C89에는 inline기능이 없었기 때문 입니다. 즉, 파일을 함수로 나누면 스택에서 값을 푸시하고 뛰어 넘는 오버 헤드가 발생했습니다. 이것은 1 개의 큰 스위치 문 (이벤트 루프)에서 코드를 구현하는 것보다 약간의 오버 헤드를 추가했습니다. 그러나 이벤트 루프는 모듈화 된 솔루션보다 항상 효율적으로 (또는 정확하게) 구현하기가 훨씬 어렵습니다. 따라서 대규모 프로젝트의 경우 사람들은 여전히 ​​모듈화를 선택하지 않습니다. 그러나 사전에 설계를 고려하고 1 개의 스위치 문으로 상태를 제어 할 수 있었을 때,이를 선택했습니다.

현재 C에서도 C 함수에서도 인라인 될 수 있기 때문에 모듈화를 위해 성능을 희생 할 필요가 없습니다.


2
C 함수는 요즘처럼 89에서 인라인이 될 수 있습니다. 인라인은 거의 사용하지 않아야 할 것입니다. 컴파일러는 거의 모든 상황에서 사용자보다 잘 알고 있습니다. 그리고이 4k LOC 파일의 대부분은 하나의 거대한 기능이 아닙니다. 이는 눈에 띄는 성능상의 이점이없는 끔찍한 코딩 스타일입니다.
Voo

@Voo, 왜 코딩 스타일을 언급했는지 모르겠습니다. 나는 그것을 옹호하지 않았다. 실제로 필자는 대부분의 경우 구현이 중단되어 덜 효율적인 솔루션을 보장한다고 언급했습니다. 또한 규모가 크지 않기 때문에 나쁜 아이디어라고 언급했습니다. 하드웨어에 근접한 네트워킹 코드에서 발생하는 매우 엄격한 루프에서 불필요하게 스택을 켜고 끄는 값 (함수 호출시)은 프로그램 실행 비용을 증가시킵니다. 이것은 훌륭한 해결책이 아닙니다. 그러나 당시 가장 좋은 제품이었습니다.
Dmitry Rubanovich

2
필수 참고 사항 : 인라인 키워드는 인라인 최적화와 관련이 거의 없습니다. 컴파일러가 최적화를 수행하는 특별한 힌트는 아니지만 대신 중복 기호로 연결해야합니다.
hyde

@Dmitry 요점은 inlineC89 컴파일러에 키워드 가 없기 때문에 인라인 할 수 없기 때문에 하나의 거대한 함수로 모든 것을 작성 해야하는 이유가 잘못 되었다는 것입니다. inline성능 최적화로 사용해서는 안됩니다 . 컴파일러는 일반적으로 사용자보다 더 잘 알고 있습니다 (키워드도 무시할 수 있음).
Voo

@Voo : 프로그래머와 컴파일러는 일반적으로 상대방이 모르는 것을 알고 있습니다. inline키워드는 인라인 최적화를 수행할지 여부의 문제보다 더 중요 링커 관련 의미를 가지고 있지만, 일부 구현에 라이닝 제어와 같은 일들이 때로는 매우 중요 할 수있는 다른 지침을 가지고있다. 경우에 따라 함수가 너무 커서 인라인 할 가치가없는 것처럼 보일 수 있지만, 일정한 폴딩으로 인해 크기와 실행 시간이 거의 줄어들지 않을 수 있습니다.
인라인

1

이것은 아직 진화론의 한 예입니다.

어두운 시절에는 단일 FILE을 컴파일하는 데 몇 분이 걸릴 수 있습니다. 프로그램이 모듈화 된 경우 필요한 헤더 파일을 포함하면 (사전 컴파일 된 헤더 옵션 없음) 속도 저하의 중요한 추가 원인이됩니다. 또한 컴파일러는 자동 스왑 파일의 이점없이 디스크 자체에 일부 정보를 유지하도록 선택 / 필요할 수 있습니다.

이러한 환경 적 요인으로 인해 지속적인 개발 관행이 이어지고 시간이 지남에 따라 천천히 적응해 왔습니다.

당시 단일 파일을 사용함으로써 얻는 이득은 HDD 대신 SSD를 사용하여 얻는 것과 비슷합니다.

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