수정 이력(1건, 최종 수정 2026년 09월 01일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- 일본어 원문의 완역으로 다시 번역했습니다. 기존 한국어판은 원문의 일부만 옮긴 축약본이라 절, 표, Mermaid 그림, 그림 설명, FAQ가 빠져 있었습니다. 일본어 원문에 맞춰 이들을 모두 복원했고, 기술적인 주장은 일본어판과 같습니다. 수정 전 버전 보기 (DOI: 10.5281/zenodo.21635181)
- 최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635180)
이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.
小村 豪 (2026). 「갑자기 COBOL 소스를 읽을 처지가 되었을 때 최저한 알아두어야 할 것 - DIVISION / PIC / COMP-3 / COPY / PERFORM을 먼저 정리」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635180 https://comcomponent.com/ko/blog/2026/03/17/001-cobol-minimum-reading-guide/
- DOI(최신 버전)
- 10.5281/zenodo.21635180
- DOI(이 버전)
- 10.5281/zenodo.22217461
인계, 장애 대응, 벤더제 패키지의 유지보수. 그런 상황에서 어느 날 갑자기 COBOL 소스가 날아오는 일이 있습니다.
- 파일명은
.cbl이나.cpy - 변수명은 전부 대문자
01,05,77,88이 줄지어 있다PIC S9(7)V99 COMP-3같은, 주문과 회계 소프트웨어의 중간 같은 표기가 나온다- 게다가
COPY투성이라서, 열어 놓은 파일만으로는 전체가 보이지 않는다
이쯤에서 대부분의 사람은 일단 손이 멈춥니다.
다만 읽기 위한 지도는 그리 크지 않습니다. COBOL에는 처리계나 제품별 차이가 있지만, 기존 업무 시스템을 읽을 때 먼저 짚어야 할 골격은 상당히 공통됩니다. 이 글에서는 IBM 계열이나 전형적인 업무 COBOL을 염두에 두고, 갑자기 소스를 읽게 된 사람을 위한 최소 세트를 정리합니다.
flowchart TB
accTitle: 손이 멈추는 이유와 지도의 크기
accDescr: 전부 대문자인 변수명이나 레벨 번호, PIC S9(7)V99 COMP-3 같은 표기, COPY투성이라서 전체가 보이지 않는 것 때문에 손이 멈추지만, 기존 업무 시스템을 읽기 위해 먼저 짚어야 할 골격은 상당히 공통되어 지도가 그리 크지 않다는 것을 보여주는 그림.
iv1["낯선 표기 앞에서 손이 멈춘다"] --> iv2["다만 골격은 처리계를 넘어 공통"]
iv2 --> iv3["최소 세트의 지도만 먼저 가진다"]
iv1 -.-> iv4["COPY투성이라 전체가 보이지 않는다"]
그림 1: 주문처럼 보이는 표기의 정체는 몇 개 안 되는 공통 개념으로 집약된다.
1. 먼저 결론(한마디로)
먼저 꽤 거칠지만 실무에 도움이 되는 말로 하면 이렇습니다.
- COBOL은 로직의 언어이기 전에, 꽤 강하게 레코드 정의의 언어입니다
PROCEDURE DIVISION만 읽어서는 절반밖에 알 수 없습니다. 먼저DATA DIVISION을 봅니다PIC는 항목의 형,USAGE는 어떤 표현으로 가질지입니다COMP-3는 packed decimal입니다. 금액이나 건수의 세계에서 자주 나옵니다88은 별도의 변수라기보다, 직전 항목의 값에 이름을 붙인 조건명입니다REDEFINES는 같은 메모리를 다른 형태로 보는 구조입니다. 복사가 아닙니다COPY가 있다면 지금 열려 있는 소스는 아직 미완성입니다. copybook을 보지 않으면 전체가 보이지 않습니다PERFORM,IF,EVALUATE,READ,WRITE,CALL을 따라가면 대체적인 흐름을 잡을 수 있습니다- 오래된 소스는 열 위치에 의미가 있는 고정 형식입니다. 겉보기의 공백이 단순한 장식이 아닙니다1
요컨대, DIVISION, PIC, USAGE, COMP-3, REDEFINES, OCCURS, 88, COPY, PERFORM. 이 부분이 읽히면 길을 잃을 확률이 꽤 낮아집니다.
이 글의 지식 맵
COBOL은 PROCEDURE DIVISION에 의한 처리 절차의 언어이기 전에 DATA DIVISION에서 레코드의 형태를 정의하는 언어이며, 각 데이터 항목은 PICTURE로 형을, USAGE로 COMP-3 같은 내부 표현을 지정합니다. COMP-3는 10진 숫자를 2자리씩 1바이트에 채워 넣는 packed decimal로, 텍스트로 열면 의미를 알 수 없는 바이트열로 보입니다. REDEFINES는 같은 영역을 다른 형태로 보고, OCCURS는 배열을, 88 레벨은 직전 항목의 값에 이름을 붙인 조건을 나타냅니다. COPY 문은 외부의 copybook을 컴파일 시점에 include하므로, 열려 있는 소스만으로는 전체 모습이 보이지 않으며, PERFORM 문과 scope terminator로 처리의 흐름을 따라가고 FILE STATUS나 EXEC SQL, EXEC CICS로 외부 경계를 파악할 필요가 있습니다.
flowchart LR
accTitle: COBOL 읽는 법
accDescr: COBOL이 DATA DIVISION과 PROCEDURE DIVISION을 기둥으로 하고, PICTURE·USAGE·COMP-3·REDEFINES·OCCURS·88 레벨·COPY 문 같은 데이터 정의 요소와, PERFORM 문이나 scope terminator 같은 제어 요소, EBCDIC이나 FILE STATUS·EXEC SQL·EXEC CICS 같은 외부 경계의 요소가 어떻게 관계되는지를 보여주는 그림
cobol["COBOL"]
cobol_data_division["DATA DIVISION"]
comp_3["COMP-3(packed decimal)"]
cobol_procedure_division["PROCEDURE DIVISION"]
cobol_picture_clause["PICTURE 절(PIC)"]
cobol_usage_clause["USAGE 절"]
comp_5["COMP-5"]
cobol_redefines["REDEFINES 절"]
cobol_occurs["OCCURS 절"]
cobol_occurs_depending_on["OCCURS DEPENDING ON"]
cobol_88_level["88레벨(조건명)"]
cobol_copy["COPY문"]
copybook["copybook"]
cobol_perform["PERFORM 문"]
cobol_scope_terminator["범위 종료자(scope terminator)"]
cobol_move["MOVE 문"]
cobol_fixed_format["고정 형식(참조 형식)"]
ibm_enterprise_cobol["IBM Enterprise COBOL"]
ebcdic["EBCDIC"]
cobol_file_status["FILE STATUS절"]
exec_sql["EXEC SQL"]
exec_cics["EXEC CICS"]
cobol -->|"이용한다"| cobol_data_division
cobol -->|"이용한다"| cobol_procedure_division
cobol_data_division -->|"이용한다"| cobol_picture_clause
cobol_data_division -->|"이용한다"| cobol_usage_clause
cobol_usage_clause -->|"이용한다"| comp_3
cobol_usage_clause -->|"이용한다"| comp_5
cobol_data_division -->|"이용한다"| cobol_redefines
cobol_data_division -->|"이용한다"| cobol_occurs
cobol_occurs_depending_on -->|"이용한다"| cobol_occurs
cobol_data_division -->|"이용한다"| cobol_88_level
cobol -.->|"이용한다"| cobol_copy
cobol_copy -->|"이용한다"| copybook
cobol_procedure_division -->|"이용한다"| cobol_perform
cobol_procedure_division -->|"이용한다"| cobol_scope_terminator
cobol_procedure_division -->|"이용한다"| cobol_move
cobol -.->|"이용한다"| cobol_fixed_format
ibm_enterprise_cobol -->|"이용한다"| ebcdic
ibm_enterprise_cobol -.->|"이용한다"| cobol_fixed_format
cobol -.->|"이용한다"| cobol_file_status
cobol -.->|"이용한다"| exec_sql
cobol -.->|"이용한다"| exec_cics
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 21건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. COBOL은 먼저 “데이터의 형”의 언어라고 생각한다
C#이나 Java의 감각으로 읽으면, 처음에는 if나 for나 함수 호출을 따라가고 싶어집니다.
하지만 COBOL은 그곳으로 가기 전에 “이 프로그램은 어떤 레코드를 받고, 어떤 레코드를 만들며, 어떤 버퍼를 갖고 있는가”를 짚어 두는 편이 빠릅니다.
전형적인 업무 COBOL은 대체로 다음 흐름입니다.
- 파일이나 DB에서 레코드를 읽는다
WORKING-STORAGE상의 항목에 넣는다- 조건 분기한다
- 다른 레코드로 다시 채운다
- 써낸다
즉, 알고리즘보다 레이아웃이 먼저 서기 쉽습니다.
flowchart TB
accTitle: 전형적인 업무 COBOL의 흐름
accDescr: 파일이나 DB에서 레코드를 읽고, WORKING-STORAGE 상의 항목에 넣고, 조건 분기하고, 다른 레코드로 다시 채워서 써내는, 전형적인 업무 COBOL의 흐름을 보여주는 그림.
f1["레코드를 읽는다"] --> f2["WORKING-STORAGE에 넣는다"]
f2 --> f3["조건 분기한다"]
f3 --> f4["다른 레코드로 다시 채운다"]
f4 --> f5["써낸다"]
그림 2: 주역은 레코드의 흐름이고, 알고리즘은 그 사이에 끼어든다.
예를 들어, 이런 골격입니다.
IDENTIFICATION DIVISION.
PROGRAM-ID. SAMPLE01.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT SALES-FILE ASSIGN TO ...
DATA DIVISION.
FILE SECTION.
FD SALES-FILE.
01 SALES-REC.
05 SALE-ID PIC 9(8).
05 SALE-AMOUNT PIC S9(7)V99 COMP-3.
WORKING-STORAGE SECTION.
01 WS-EOF PIC X VALUE 'N'.
88 EOF VALUE 'Y'.
PROCEDURE DIVISION.
PERFORM UNTIL EOF
READ SALES-FILE
AT END
SET EOF TO TRUE
NOT AT END
PERFORM PROCESS-SALE
END-READ
END-PERFORM
STOP RUN.
이 코드를 읽을 때 먼저 봐야 하는 것은 PERFORM보다 먼저 SALE-AMOUNT의 형이나 EOF의 의미입니다.
COBOL은 그 순서로 읽으면 갑자기 조용해집니다.
3. 4개의 DIVISION을 먼저 본다
COBOL 소스는 먼저 크게 4개의 DIVISION으로 나뉩니다.
| DIVISION | 먼저 볼 것 |
|---|---|
IDENTIFICATION DIVISION |
프로그램명, 오래된 주석, 유래 |
ENVIRONMENT DIVISION |
파일, 외부 자원, 입출력의 전제 |
DATA DIVISION |
레코드 정의, 작업 영역, 인수 |
PROCEDURE DIVISION |
실제 처리 절차 |
특히 중요한 것은 다음 부분입니다.
FILE SECTION입출력 파일의 레코드 정의가 있다WORKING-STORAGE SECTION평소 쓰는 변수, 플래그, 카운터, 작업 버퍼가 있다LOCAL-STORAGE SECTION호출할 때마다 초기화되는 영역이 있을 수 있다LINKAGE SECTION외부에서 넘겨받는 인수나, 서브프로그램의 수신 창구가 있을 수 있다
LINKAGE SECTION과 PROCEDURE DIVISION USING ...이 보이면, 그 프로그램은 단독으로 완결되지 않고, 외부에서 데이터를 받아 동작할 가능성이 높습니다.
flowchart TB
accTitle: LINKAGE SECTION이 보여주는 것
accDescr: LINKAGE SECTION과 PROCEDURE DIVISION USING이 보이면, 그 프로그램은 단독으로 완결되지 않고 외부에서 데이터를 받아 동작할 가능성이 높다는 것을 보여주는 그림.
lk1["LINKAGE SECTION이 있다"] --> lk3["외부에서 데이터를 받아 동작할 가능성"]
lk2["PROCEDURE DIVISION USING이 있다"] --> lk3
lk3 -.-> lk4["단독으로 완결되는 프로그램이 아니다"]
그림 3: 수신 창구의 정의가 보이면, 호출하는 쪽의 존재를 전제로 읽는다.
4. 고정 형식의 겉모습에 겁먹지 않는다
오래된 COBOL에서는 소스 한 줄의 열 위치 그 자체에 의미가 있습니다. 이것을 모른 채 보면 “왜 왼쪽에 이상한 여백이 있는지”가 영원히 알 수 없습니다.1
고정 형식에서는 대략 이렇습니다.
- 1 - 6열: 일련번호
- 7열: indicator
- 8 - 11열: Area A
- 12 - 72열: Area B
7열째가 특히 중요합니다.
*또는/: 주석 행-: 계속 행D: debugging line*>: 도중에도 쓸 수 있는 주석
열과 내용의 대응을, 눈금이 달린 형태로 보면 이렇게 됩니다. 1행이 10의 자리, 2행이 1의 자리 눈금입니다.
1 2 3 4 5 6 7 8
12345678901234567890123456789012345678901234567890123456789012345678901234567890
SSSSSSIAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB........
S= 일련번호 (1 - 6열)I= indicator (7열)A= Area A (8 - 11열)B= Area B (12 - 72열).= 73열 이후. 처리계에 따라 식별란으로 쓰이기도 하지만, 프로그램의 의미에는 관여하지 않습니다
실제 소스에 대입하면 이렇게 됩니다.
000100* 이 행은 7열째가 * 이므로 주석
000200 IDENTIFICATION DIVISION.
000300 PROGRAM-ID. SAMPLE01.
000400 DATA DIVISION.
000500 WORKING-STORAGE SECTION.
000600 01 WS-ORDER.
000700 05 WS-ORDER-ID PIC 9(8).
000800 05 WS-LONG-NAME PIC X(30) VALUE 'ABCDEFGHIJKLMNOPQRST
000900- 'UVWXYZ0123'.
읽는 방법의 포인트는 4가지입니다.
- 1 - 6열은 일련번호입니다. 위 예에서
000100처럼 매겨져 있는 것이 그것으로, 프로그램의 동작과는 관계없습니다. 공백인 경우도 있습니다. - 7열째가 indicator입니다.
*이면 주석,-이면 계속 행. 위 예의000900행이 그것으로, 앞 행의 문자열 리터럴이 이어지고 있습니다. - 8 - 11열이 Area A입니다.
DIVISION,SECTION, 단락명,FD, 그리고01과77레벨 번호는 여기서 시작합니다. 위 예의IDENTIFICATION DIVISION.과01 WS-ORDER.가 Area A로 시작합니다. - 12 - 72열이 Area B입니다. 일반 문장과,
05같은 하위 레벨은 여기에 씁니다. 위 예의05 WS-ORDER-ID가 Area B로 시작합니다.
여기서의 공백은 현대적인 의미의 “서식 정리”가 아니라, 부분적으로 구문입니다. 에디터에서 탭 변환하거나, 왼쪽으로 몰거나, 대충 복사 붙여넣기하면 그대로 망가집니다. 오래된 소스를 볼 때는 먼저 그 파일이 fixed format인지 free format인지를 의심하세요. fixed 형식 소스에 현대적인 자동 서식 정리를 걸면 Area A와 Area B의 경계가 무너져 컴파일이 통과하지 않게 됩니다.
flowchart TB
accTitle: 고정 형식 소스를 건드리기 전에
accDescr: 오래된 소스에서는 먼저 fixed format인지 free format인지 확인하고, fixed 형식 소스에 현대적인 자동 서식 정리나 탭 변환을 걸면 Area A와 Area B의 경계가 무너져 컴파일이 통과하지 않게 됨을 보여주는 그림.
fx1["오래된 소스를 열었다"] --> fx2{"fixed인가 free인가"}
fx2 -->|"fixed 형식"| fx3["열 위치 자체가 구문"]
fx3 -.-> fx4["자동 서식 정리나 탭 변환으로 망가진다"]
fx2 -->|"free 형식"| fx5["열의 제약이 느슨하다"]
그림 4: 서식 정리보다 먼저, 그 파일의 형식을 확정한다.
5. DATA DIVISION의 최소한
5.1 레벨 번호
COBOL의 데이터 정의는 들여쓰기가 아니라 레벨 번호로 계층을 만듭니다.2
01 WS-ORDER.
05 WS-ORDER-ID PIC 9(8).
05 WS-AMOUNT PIC S9(7)V99 COMP-3.
05 WS-STATUS PIC X.
88 WS-OK VALUE '0'.
88 WS-ERROR VALUE '9'.
77 WS-COUNT PIC 9(4).
최소한 이것만 기억해 두면 충분합니다.
01: 한 덩어리의 최상위 레코드, 그룹02-49: 그 아래 계층77: 독립된 단일 항목88: condition-name. 직전 항목의 값에 이름을 붙인다366:RENAMES용. 마주칠 확률은 높지 않지만 존재는 한다
중요한 것은 88을 bool의 별도 변수라고 생각하지 않는 것입니다.
WS-OK라는 영역이 별도로 있는 것이 아니라, WS-STATUS가 '0'일 때 WS-OK라는 이름으로 읽을 수 있다는 느낌입니다.
flowchart TB
accTitle: 88 레벨의 정체
accDescr: 88 레벨은 독립된 bool 변수가 아니라 직전 항목의 값에 이름을 붙인 조건명이며, WS-STATUS가 특정 값일 때 WS-OK라는 이름으로 읽을 수 있을 뿐임을 보여주는 그림.
cn1["기저 항목 WS-STATUS"] --> cn2{"값이 무엇인가"}
cn2 -->|"0일 때"| cn3["WS-OK라는 이름으로 참이 된다"]
cn2 -->|"9일 때"| cn4["WS-ERROR라는 이름으로 참이 된다"]
cn3 -.-> cn5["별도의 영역은 존재하지 않는다"]
그림 5: 88은 변수가 아니라, 값에 붙인 읽기 쉬운 별명이다.
하나 더 중요한 것은, 계층을 결정하는 것은 공백이 아니라 레벨 번호라는 점입니다.
겉모습의 들여쓰기는 참고는 되지만, 최종적으로 믿어야 할 것은 01 / 05 / 10 / 88 쪽입니다.2
5.2 PICTURE
PIC는 그 항목의 형을 나타냅니다.
가장 자주 보는 것은 다음과 같습니다.
| 표기 | 대략적인 의미 |
|---|---|
X |
문자 |
9 |
숫자 |
S |
부호 있음 |
V |
소수점은 논리상으로만 가진다 |
X(10) |
10문자 |
9(5) |
5자리 수치 |
S9(7)V99 |
부호 있음, 정수 7자리 + 소수 2자리 |
예를 들면,
PIC X(10)→ 10문자PIC 9(5)V99→ 5자리 정수 + 2자리 소수PIC S9(7)V99→ 부호 있는 7자리 정수 + 2자리 소수
입니다.
여기서 특히 중요한 것은 V입니다.
V는 실제 . 문자를 갖지 않습니다.
PIC 9(5)V99는 “소수점 2자리의 수”로 다루어지지만, 데이터 위에 점 문자가 들어 있는 것은 아닙니다.
그래서 파일이나 덤프를 “겉모습의 문자열”로 해석하면 대개 넘어집니다.
flowchart TB
accTitle: V는 논리상의 소수점
accDescr: PIC 9(5)V99의 V는 논리상의 소수점이며 실제 점 문자를 갖지 않고 데이터에 점이 들어가지 않으므로, 파일이나 덤프를 겉모습의 문자열로 해석하면 넘어짐을 보여주는 그림.
vd1["PIC 9(5)V99"] --> vd2["소수점 2자리의 수로 다룬다"]
vd2 --> vd3["데이터에 점 문자는 들어가지 않는다"]
vd3 -.-> vd4["겉모습의 문자열로 해석하면 넘어진다"]
그림 6: 소수점은 정의 안에만 있고, 데이터 안에는 없다.
5.3 USAGE / DISPLAY / COMP / COMP-3
PIC가 형이라면, USAGE는 어떤 표현으로 보유할지입니다.
최소한 다음만 짚으면 상당히 읽을 수 있습니다.45
| 표기 | 대략적인 의미 | 읽을 때 주의점 |
|---|---|---|
DISPLAY |
문자로 보이는 외부 10진 | mainframe에서는 EBCDIC이 전제인 경우가 있다6 |
COMP / BINARY |
2진수 | 겉보기 자릿수와 내부 표현은 다르다 |
COMP-3 / PACKED-DECIMAL |
packed decimal | 문자로 읽으면 망가져 보인다 |
예를 들면,
01 WS-AMOUNT-DISP PIC S9(7)V99.
01 WS-AMOUNT-BIN PIC S9(7) COMP.
01 WS-AMOUNT-PACK PIC S9(7)V99 COMP-3.
이 3개는 전부 “수치”이지만, 내부의 보유 방식이 다릅니다.
flowchart TB
accTitle: 같은 수치라도 보유 방식이 다르다
accDescr: 같은 부호 있는 수치 항목이라도, DISPLAY는 문자로 보이는 외부 10진, COMP는 2진수, COMP-3는 packed decimal로, USAGE에 따라 내부의 보유 방식이 다름을 보여주는 그림.
us1["같은 형의 수치 항목"] --> us2["DISPLAY(문자로 보이는 형태)"]
us1 --> us3["COMP(2진수)"]
us1 --> us4["COMP-3(packed decimal)"]
us4 -.-> us5["문자로 읽으면 망가져 보인다"]
그림 7: PIC가 같아도, USAGE가 바뀌면 바이트열은 전혀 다른 것이 된다.
실무에서 가장 효과적인 것은 COMP-3를 본 순간의 반응입니다.
- 그것은 packed decimal이다
- 아마 금액, 세액, 건수, 레이트 계열이다
- 텍스트로 보면 망가져 보이는 것이 당연하다
- CSV나 UTF-8의 기분으로 들여다보면 사고가 난다
라는 이해를 갖고 있으면, 덤프나 바이너리 파일의 겉모습에서 쓸데없이 당황하지 않을 수 있습니다.
flowchart TB
accTitle: COMP-3를 본 순간의 반응
accDescr: COMP-3를 보면 packed decimal임을 알고, 아마 금액이나 세액이나 건수나 레이트 계열의 항목을 의심하며, 텍스트로 보면 망가져 보이는 것이 당연하므로 CSV나 UTF-8의 기분으로 들여다보지 않는다는 반응을 몸에 익히는 것을 보여주는 그림.
cp1["COMP-3를 발견했다"] --> cp2["packed decimal임을 안다"]
cp2 --> cp3["금액이나 건수 항목을 의심한다"]
cp3 --> cp4["텍스트로 망가져 보여도 당황하지 않는다"]
그림 8: 이 반사만으로도, 덤프 앞에서 당황하는 횟수가 줄어든다.
COMP-3가 실제로 어떤 바이트열이 되는가
여기는 한 번만 손으로 따라가 보면 이후 보이는 방식이 달라집니다.
- 1바이트에 10진 숫자를 2자리씩 채워 넣는다
- 단 가장 오른쪽 바이트만 하위 1자리와 부호로 1바이트를 쓴다
부호는 4비트 값으로 표현되며, C가 양수, D가 음수, F가 부호 없음입니다.
IBM 매뉴얼에 실려 있는 예로 확인해 봅니다.4
| 정의 | 값 | 바이트열 |
|---|---|---|
PIC S9(4) PACKED-DECIMAL |
+1234 |
01 23 4C |
PIC S9(4) PACKED-DECIMAL |
-1234 |
01 23 4D |
PIC 9(4) PACKED-DECIMAL |
1234 |
01 23 4F |
1234는 4자리이므로, 규칙 2 때문에 선두에 1자리만큼의 빈자리가 생깁니다. 그것이 선두의 0입니다.
같은 요령으로, 이 글에 여러 번 나온 PIC S9(7)V99 COMP-3를 따라가 봅니다.
S9(7)V99는 정수 7자리 + 소수 2자리 = 9자리V는 소수점 위치를 나타낼 뿐이므로, 바이트를 하나도 소비하지 않습니다- 9자리를 2자리씩 채우면 4바이트, 나머지 1자리와 부호로 1바이트. 합계 5바이트
값이 +12345.67이라면, 9자리로 맞추면 001234567이므로 이렇게 됩니다.
값 : +12345.67
9자리 표현 : 0 0 1 2 3 4 5 6 7 와 부호
바이트 : 00 12 34 56 7C
^ 부호 C = 양수
음수 -12345.67이라면, 마지막이 7D가 될 뿐입니다.
바이트 : 00 12 34 56 7D
이것을 텍스트로 열면 어떻게 보이는가가 본론입니다. 00 12 34 56 7C를 억지로 1바이트 1문자로 ASCII에 대응시키면,
00은 NUL,12는 제어 문자라서 애초에 문자로 표시할 수 없습니다34는4,56은V,7C는|
가 됩니다. 즉 화면에는 “표시할 수 없는 문자가 몇 개 이어진 뒤 4V|“처럼 보입니다. 12345.67이라는 나열은 어디에도 나타나지 않습니다.
이것이 “글자가 깨진 것처럼 보이지만 실제로는 망가지지 않은” 상태의 정체입니다. 덤프를 열었을 때 알 수 없는 나열을 만나면, 먼저 그 항목의 USAGE가 COMP-3는 아닌지를 의심하세요.
flowchart TB
accTitle: COMP-3의 바이트열이 만들어지는 방식
accDescr: packed decimal은 10진 숫자를 1바이트에 2자리씩 채워 넣고 가장 오른쪽 바이트만 하위 1자리와 부호로 쓰기 때문에 9자리 수치는 5바이트가 되고, 이를 ASCII로 열어도 원래 수치의 나열은 어디에도 나타나지 않음을 보여주는 그림.
pk1["10진 숫자를 2자리씩 1바이트로"] --> pk2["마지막 바이트는 하위 1자리와 부호"]
pk2 --> pk3["9자리면 합계 5바이트"]
pk3 --> pk4["ASCII로 열면 원래 수치가 나타나지 않는다"]
pk4 -.-> pk5["글자가 깨진 것처럼 보이지만 망가지지 않았다"]
그림 9: 채워 넣는 규칙 2가지를 알면, 알 수 없던 덤프가 읽을 수 있는 나열로 바뀐다.
덧붙여, 자릿수에서 바이트 수를 구하는 조견표도 함께 둡니다. 바이트 수는 “9의 개수를 2로 나눠 소수를 버리고, 1을 더한다”로 구할 수 있습니다.
| PICTURE의 9 개수 | COMP-3의 바이트 수 |
|---|---|
| 1 | 1 |
| 2 - 3 | 2 |
| 4 - 5 | 3 |
| 6 - 7 | 4 |
| 8 - 9 | 5 |
| 10 - 11 | 6 |
| 12 - 13 | 7 |
같은 바이트 수에 자릿수가 2개씩 늘어서는 것은, 9의 개수가 짝수일 때만 선두에 1자리 분의 빈자리가 생기기 때문입니다. S9(4)가 01 23 4C의 3바이트가 되고, S9(5)도 같은 3바이트로 들어가는 것은 그 때문입니다.
외부 파일과의 레이아웃 대조에서는, 이 표가 없으면 1바이트씩 어긋나 갑니다.
하나 더 보충하면, DISPLAY라고 해서 반드시 ASCII 문자열이라고는 할 수 없습니다.
z/OS 계열에서는 EBCDIC이 전제가 되므로, 숫자가 문자로 보이더라도, 바이트값은 ASCII의 '0' - '9'와는 다른 경우가 있습니다.6
5.4 REDEFINES / OCCURS / COPY / FILLER
이 4가지는 읽을 때 막히는 지점입니다.
REDEFINES
REDEFINES는 같은 영역을 다른 형태로 보는 구조입니다. 복사가 아닙니다.7
01 REC-BUF.
05 REC-TYPE PIC X.
05 REC-DATA PIC X(99).
01 HEADER-REC REDEFINES REC-BUF.
05 HDR-TYPE PIC X.
05 HDR-DATE PIC 9(8).
05 FILLER PIC X(91).
이것은 C 계열로 말하면 union적인 감각에 가깝습니다.
“1개의 100바이트를, 별도의 레코드 종류로 구별한다”는 식의 쓰임에서 자주 나옵니다.
flowchart TB
accTitle: REDEFINES는 같은 영역의 다른 해석
accDescr: REDEFINES는 복사가 아니라 같은 메모리 영역을 다른 형태로 보는 구조로, 하나의 버퍼를 범용 레코드로도 헤더 레코드로도 읽을 수 있고, 한쪽을 고쳐 쓰면 다른 한쪽의 보이는 방식도 바뀜을 보여주는 그림.
rd1["하나의 메모리 영역"] --> rd2["REC-BUF로 본다"]
rd1 --> rd3["HEADER-REC로 본다"]
rd2 -.-> rd4["한쪽을 쓰면 양쪽의 보이는 방식이 바뀐다"]
rd3 -.-> rd4
그림 10: 정의가 2개 있어도, 실체의 바이트열은 하나뿐이다.
OCCURS
OCCURS는 배열입니다. COBOL에서는 table이라고 불리기 쉽습니다.
05 WS-ITEM OCCURS 12 TIMES.
10 WS-PRICE PIC 9(5).
게다가 OCCURS DEPENDING ON이 나오면, 가변 길이 테이블입니다.
이 경우, 후속 항목의 위치까지 영향을 받는 일이 있으므로, 고정 길이라는 기분으로 따라가면 발을 헛디딥니다.8
flowchart TB
accTitle: OCCURS DEPENDING ON의 주의점
accDescr: OCCURS는 배열이며, OCCURS DEPENDING ON이 붙으면 가변 길이 테이블이 되어 후속 항목의 위치까지 값에 따라 움직일 수 있으므로, 고정 길이라는 기분으로 오프셋을 따라가면 발을 헛디딤을 보여주는 그림.
oc1["OCCURS를 발견했다"] --> oc2{"DEPENDING ON이 붙어 있는가"}
oc2 -->|"붙어 있지 않다"| oc3["고정 횟수의 테이블"]
oc2 -->|"붙어 있다"| oc4["가변 길이 테이블"]
oc4 -.-> oc5["후속 항목의 위치도 움직일 수 있다"]
그림 11: DEPENDING ON이라는 세 단어로, 오프셋 계산의 전제가 바뀐다.
COPY
COPY는 compile time의 include입니다.
즉, 지금 열려 있는 소스는 아직 완성형이 아닐 가능성이 있습니다.9
COPY CUSTOMER-REC.
COPY ERROR-MAP.
레코드 정의, 공통 플래그, SQL용 host variable, 외부 인터페이스가 copybook에 밀어넣어져 있는 것은 꽤 흔합니다.
COPY가 많아서 읽기 어려울 때는, 전개 후 소스나 compiler listing을 볼 수 없는지를 확인하는 편이 빠릅니다. IBM Enterprise COBOL에는 MDECK라는, 라이브러리 처리 후의 입력 소스를 출력하기 위한 option도 있습니다.10
flowchart TB
accTitle: COPY가 있는 소스의 읽는 법
accDescr: COPY는 컴파일 시점의 include이며 열려 있는 소스가 완성형이 아닐 가능성이 있으므로, copybook을 확인하거나 읽기 어려울 때는 전개 후 소스나 compiler listing, MDECK 옵션의 출력을 찾아본다는 것을 보여주는 그림.
cy1["COPY를 발견했다"] --> cy2["열려 있는 소스가 미완성일지 모른다"]
cy2 --> cy3["copybook을 열어 확인"]
cy2 -.->|"읽기 어려울 때"| cy4["전개 후 소스나 listing을 찾는다"]
그림 12: 지금 보이는 줄 수가, 그 프로그램의 전부라고는 할 수 없다.
FILLER
FILLER는 이름이 없는 항목입니다.
다만, “참조하지 않으니 의미가 없다”는 것은 아닙니다.
- 예약 영역
- 구 사양과의 호환용 구멍
- 레코드 길이 맞춤
REDEFINES용 여백
으로서 평범하게 쓰입니다.
FILLER는 이름이 없을 뿐이고, 바이트 수로서는 존재합니다. 이것을 잊으면, 외부 파일과의 매핑에서 1바이트씩 세계가 어긋나 갑니다.
flowchart TB
accTitle: FILLER를 세지 않으면 일어나는 일
accDescr: FILLER는 이름이 없을 뿐 바이트 수로서는 존재하며 예약 영역이나 레코드 길이 맞춤으로 쓰이므로, 세는 것을 잊으면 외부 파일과의 매핑이 1바이트씩 어긋나 감을 보여주는 그림.
fl1["FILLER는 이름이 없는 항목"] --> fl2["바이트 수로서는 존재한다"]
fl2 --> fl3{"레이아웃 계산에 포함했는가"}
fl3 -->|"포함했다"| fl4["외부 파일과 일치한다"]
fl3 -->|"잊었다"| fl5["1바이트씩 어긋나 간다"]
그림 13: 참조되지 않는 항목에도, 길이라는 역할이 있다.
6. PROCEDURE DIVISION의 최소한
DATA DIVISION이 지도라면, PROCEDURE DIVISION은 이동 경로입니다.
6.1 PERFORM
PERFORM은 COBOL의 기본적인 제어 이동입니다.
대략 말하면 처리를 불러서 돌아온다입니다.11
자주 보는 형태는 다음입니다.
PERFORM INIT-PROC
PERFORM UNTIL EOF
PERFORM READ-PROC
IF NOT EOF
PERFORM EDIT-PROC
PERFORM WRITE-PROC
END-IF
END-PERFORM
PERFORM에는 크게 2계통이 있습니다.
- 단락이나 절을 지정하는 out-of-line
PERFORM - 그 자리에 블록을 쓰는 inline
PERFORM ... END-PERFORM
게다가 더 오래된 코드에서는 PERFORM A-100 THRU A-199 같은 범위 지정도 흔히 나옵니다.
이것은 편리하지만, 단락을 도중에 추가하면 말려드는 사고가 일어나기 쉬우므로, 읽을 때는 범위의 끝을 제대로 봅니다.
flowchart TB
accTitle: PERFORM의 3가지 형태
accDescr: PERFORM에는 단락이나 절을 지정해서 불러서 돌아오는 out-of-line과, 그 자리에 블록을 쓰는 inline이 있고, 오래된 코드에는 THRU에 의한 범위 지정도 있어서, 범위 지정은 단락을 도중에 추가하면 말려드는 사고가 일어나기 쉬움을 보여주는 그림.
pf1["PERFORM을 발견했다"] --> pf2["단락을 부르는 out-of-line"]
pf1 --> pf3["그 자리에 쓰는 inline"]
pf1 --> pf4["THRU의 범위 지정"]
pf4 -.-> pf5["범위의 끝을 반드시 확인한다"]
그림 14: 어떤 형태의 PERFORM인지에 따라, 따라가야 할 복귀 지점과 범위가 달라진다.
6.2 IF / EVALUATE / 스코프
조건 분기는 IF가 기본입니다.
EVALUATE는 switch/case적인 것이라고 생각하면 대체로 맞습니다.
주의해야 하는 것은 스코프가 끝나는 방식입니다.12
END-IFEND-PERFORMEND-READ
같은 명시적인 종단이 있는 코드는 아직 읽기 쉽습니다.
문제는 오래된 코드입니다. COBOL에서는 .이 암묵의 scope terminator로 작동하여, 아직 닫히지 않은 문장을 한꺼번에 끝내 버립니다.12
즉, 단 하나의 마침표로,
- 어디까지가
IF인가 - 어디까지가
PERFORM인가 - 어디서 다음 sentence로 넘어가는가
가 바뀝니다.
게다가 NEXT SENTENCE는 CONTINUE와 같지 않습니다.
NEXT SENTENCE는 다음 마침표 뒤로 진행하므로, 후속 .의 위치에 따라 도착지가 달라집니다.12
오래된 COBOL을 읽을 때는 줄 끝이 아니라 마침표를 본다 정도로 딱 좋습니다.
flowchart TB
accTitle: 마침표의 무게
accDescr: END-IF 같은 명시적인 종단이 있는 코드는 읽기 쉽지만, 오래된 코드에서는 마침표가 암묵의 scope terminator로 작동해서 아직 닫히지 않은 문장을 한꺼번에 끝내 버리기 때문에, 마침표 하나의 위치로 IF나 PERFORM의 범위가 바뀜을 보여주는 그림.
sc1{"명시적인 종단이 있는가"}
sc1 -->|"END-IF 등이 있다"| sc2["범위가 읽기 쉽다"]
sc1 -->|"없는 오래된 코드"| sc3["마침표가 암묵의 종단"]
sc3 --> sc4["하나의 위치로 범위가 바뀐다"]
sc4 -.-> sc5["줄 끝이 아니라 마침표를 본다"]
그림 15: 오래된 코드의 제어 흐름은, 마침표의 위치가 쥐고 있다.
6.3 READ / WRITE / CALL
업무 COBOL에서 빈출인 것은 이 부근입니다.
READWRITEREWRITESTARTCALL
특히 READ ... AT END ...는 정석입니다.
READ IN-FILE
AT END
SET EOF TO TRUE
NOT AT END
PERFORM PROCESS-REC
END-READ
CALL 'SUBPGM' USING ...이 있으면, 다른 프로그램으로 넘어갑니다.
그때는 호출받는 쪽의 LINKAGE SECTION과 PROCEDURE DIVISION USING을 보면, 주고받는 형태가 상당히 보입니다.
flowchart TB
accTitle: CALL을 발견했을 때 따라가는 법
accDescr: CALL이 있으면 다른 프로그램으로 넘어가므로, 호출받는 쪽의 LINKAGE SECTION과 PROCEDURE DIVISION USING을 보면 인수를 주고받는 형태가 보임을 보여주는 그림.
ca1["CALL을 발견했다"] --> ca2["다른 프로그램으로 넘어간다"]
ca2 --> ca3["호출받는 쪽의 LINKAGE SECTION을 본다"]
ca3 --> ca4["USING으로 주고받는 형태를 파악한다"]
그림 16: 호출의 의미는, 호출받는 쪽의 수신 창구와 세트로 읽는다.
7. COBOL의 바깥에 있는 것
COBOL은 소스만으로 세계가 완결되지 않는 경우가 꽤 있습니다.
- 파일 정의
- 실행 환경
- DB 접속
- 트랜잭션 환경
- job 제어
가 바깥쪽에 나뉘어 있기 때문입니다.
최소한 다음을 짚으면 읽기 쉬워집니다.
파일과 FILE STATUS
ENVIRONMENT DIVISION의 FILE-CONTROL과, DATA DIVISION의 FILE SECTION / FD는 세트로 읽습니다.13
SELECT IN-FILE ASSIGN TO ...
FILE STATUS IS WS-FS.
FD IN-FILE.
01 IN-REC.
05 ...
FILE STATUS가 있으면, 각 I/O 후의 결과 코드가 들어갑니다.
파일 계열 장애나 EOF 판정을 읽을 때는, 이것을 보지 않으면 시작조차 안 됩니다.14
flowchart TB
accTitle: 파일 정의는 세트로 읽는다
accDescr: ENVIRONMENT DIVISION의 FILE-CONTROL에 있는 SELECT와, DATA DIVISION의 FD는 세트로 하나의 파일 정의이며, FILE STATUS가 있으면 각 입출력 후의 결과 코드가 들어가므로, 파일 계열 장애와 EOF 판정은 여기서부터 읽는다는 것을 보여주는 그림.
fs1["FILE-CONTROL의 SELECT"] --> fs3["세트로 하나의 파일 정의"]
fs2["FILE SECTION의 FD"] --> fs3
fs3 --> fs4["FILE STATUS에 결과 코드가 들어간다"]
fs4 -.-> fs5["장애와 EOF 판정 해독의 시작점"]
그림 17: 파일의 모습은, 2개의 DIVISION에 나뉘어 쓰여 있다.
EXEC SQL
이것이 나오면 embedded SQL입니다.
EXEC SQL
SELECT ...
END-EXEC.
이 경우, COBOL은 “host 변수의 그릇”이고, 실제 취득 조건이나 갱신 대상은 SQL 쪽에 있습니다.
그래서 EXEC SQL의 내용을 보통의 SQL로 읽는 것이 지름길입니다.
EXEC CICS
이것이 나오면 CICS의 트랜잭션 문맥입니다.15
EXEC CICS
RECEIVE MAP(...)
END-EXEC.
이 순간, 단순한 배치 해독이 아니게 됩니다. 화면, 트랜잭션, 응답 코드, COMMAREA 등, 외부 문맥까지 포함해서 읽을 필요가 있습니다.
JCL이나 실행 정의
mainframe batch에서는 실제로 어느 데이터셋이 할당되는지나 어떤 순서로 job이 흐르는지가 COBOL 소스 바깥에 있는 경우도 드물지 않습니다. 소스만 봐서는 “이 파일은 어디에 있는가”를 알 수 없을 때는, 코드가 나쁜 것이 아니라 보고 있는 범위가 아직 부족할 뿐인 경우가 흔히 있습니다.
flowchart TB
accTitle: 소스 바깥에 있는 세계
accDescr: EXEC SQL이 나오면 실제 취득 조건은 SQL 쪽에, EXEC CICS가 나오면 화면이나 트랜잭션 같은 외부 문맥에, mainframe batch에서는 데이터셋 할당이나 job의 흐름이 JCL에 있다는 식으로, COBOL 소스의 바깥쪽에 세계가 나뉘어 있음을 보여주는 그림.
ex1["EXEC SQL"] --> ex7["소스 바깥도 본다"]
ex3["EXEC CICS"] --> ex7
ex5["JCL이나 실행 정의"] --> ex7
ex7 -.-> ex2["SQL의 조건 · 화면 문맥 · job의 흐름"]
그림 18: 알 수 없는 것은 코드 탓이 아니라, 보고 있는 범위가 좁을 뿐인 경우가 있다.
처리계(컴파일러)의 차이
이 글은 IBM 계열을 염두에 두고 썼지만, 실무에서는 Micro Focus나, Linux / Windows 위의 COBOL을 만나는 경우도 있습니다. 골격은 공통이므로 읽는 법은 달라지지 않지만, “같은 작성법인데 결과가 다르다”는 포인트는 정해져 있으므로, 먼저 파악해 두면 사고를 피할 수 있습니다.
| 보는 곳 | z/OS 계열(IBM Enterprise COBOL) | 오픈 계열(Micro Focus, Linux / Windows판 등) |
|---|---|---|
| 문자 코드 | EBCDIC이 전제6 | ASCII가 전제6 |
| 참조 형식 | fixed 형식이 전통적인 기본값. free 형식도 선택 가능1 | fixed와 free를 모두 가지며, 기본값이 어느 쪽인지는 빌드 설정에 따라 다르다1 |
| copybook을 찾는 법 | 라이브러리 지정 | 컴파일러 옵션에서의 검색 경로 지정 |
| 방언 | — | “어느 처리계에 맞출지”를 전환하는 옵션이 있다 |
특히 효과가 큰 것이 문자 코드입니다. 같은 PIC X(10)이라도, z/OS에서 작성한 파일을 그대로 Windows 쪽에서 읽으면, 숫자조차 다른 바이트값이 됩니다. “이관했더니 전부 깨졌다”의 많은 경우가 이것이며, COMP-3의 이야기와는 별개 문제입니다.
하나 더, 수치의 내부 표현에도 차이가 나기 쉬운 부분이 있습니다. IBM Enterprise COBOL에서는, BINARY / COMP-4는 PICTURE에 적은 자릿수에서 잘림이 발생하는 데 비해, COMP-5는 2 / 4 / 8바이트라는 네이티브한 2진수 용량까지 값을 가지며, 잘림도 바이너리 크기 쪽에서 일어납니다.16 즉 PIC S9(4) COMP와 PIC S9(4) COMP-5는 비슷해 보여도 들어갈 수 있는 값의 상한이 다릅니다. 다른 시스템과 2진수 그대로 값을 주고받는 코드에서 COMP-5가 나온다면, 일부러 그렇게 쓴 것이라고 생각하고 읽으세요.
자신의 환경과 비교할 때의 가장 빠른 절차는 다음과 같습니다.
- 빌드 정의(makefile, JCL, 프로젝트 설정)를 먼저 연다. 어느 처리계의, 어느 옵션으로 컴파일되는지가, 소스보다 먼저 파악됩니다.
- 참조 형식(fixed / free)을 확정한다. 여기를 틀린 채로 에디터에서 정리하면 망가집니다.
- 문자 코드를 확정한다. EBCDIC인지 ASCII인지에 따라, 덤프를 읽는 법이 달라집니다.
COMP계열 항목에 표시를 해둔다. 처리계 차이가 나오는 것은 거의 여기입니다.
flowchart TB
accTitle: 자신의 환경과 비교하는 가장 빠른 절차
accDescr: 빌드 정의를 먼저 열어 어느 처리계와 옵션인지 확인하고, 참조 형식이 fixed인지 free인지 확정하고, 문자 코드가 EBCDIC인지 ASCII인지 확정하고, 처리계 차이가 나기 쉬운 COMP 계열 항목에 표시를 해두는 절차를 보여주는 그림.
ck1["빌드 정의를 먼저 연다"] --> ck2["참조 형식을 확정한다"]
ck2 --> ck3["문자 코드를 확정한다"]
ck3 --> ck4["COMP 계열 항목에 표시를 해둔다"]
그림 19: 소스를 읽어 들이기 전의 4단계로, 처리계 차이로 인한 사고를 막을 수 있다.
8. 최소한의 읽는 순서
갑자기 COBOL을 읽게 되었을 때는 다음 순서가 안전합니다.
COPY를 전부 훑는다 copybook을 열 수 있으면 연다. 무리라면 listing이나 전개 후 소스를 찾는다01레벨의 레코드 정의를 줍는다FILE SECTION,WORKING-STORAGE,LINKAGE SECTION의 최상위를 목록화한다PIC와USAGE를 읽는다 금액, 날짜, 건수, 코드, 플래그를 식별한다READ/WRITE/REWRITE/CALL/EXEC SQL/EXEC CICS를 검색한다 입출력과 외부 경계를 먼저 파악한다- 최초의 주 경로만 따라간다
PROCEDURE DIVISION의 선두부터PERFORM연쇄를 짚어 간다 88과 status 항목을 본다 EOF, 정상/이상, 종별 코드의 의미가 읽기 쉬워진다REDEFINES/OCCURS DEPENDING ON/COMP-3에 표시를 해둔다 나중에 반드시 영향을 미치므로, 먼저 위험물로서 표시해 둔다- 파일이라면
FILE STATUS를 본다 I/O 오류 계열의 오독을 상당히 줄일 수 있다
이 순서라면, 갑자기 전문을 정독하지 않아도 됩니다. COBOL은 처음부터 100% 이해하려 하기보다, 레코드, 외부 경계, 주 경로의 3가지를 짚은 다음 세부로 가는 편이 훨씬 편합니다.
flowchart TB
accTitle: 안전한 읽는 순서
accDescr: COPY를 훑어서 copybook을 확인하고, 01 레벨의 레코드 정의를 목록화하고, PIC와 USAGE로 항목의 형을 읽고, 입출력과 외부 경계를 검색으로 파악한 뒤, PROCEDURE DIVISION 선두의 PERFORM 연쇄로 주 경로를 따라가고, 위험물에 표시를 해두는 읽는 순서를 보여주는 그림.
ro1["COPY를 전부 훑는다"] --> ro2["01 레벨을 목록화한다"]
ro2 --> ro3["PIC와 USAGE로 형을 읽는다"]
ro3 --> ro4["입출력과 외부 경계를 검색한다"]
ro4 --> ro5["주 경로의 PERFORM 연쇄를 따라간다"]
ro5 -.-> ro6["위험물에 표시를 해둔다"]
그림 20: 전문 정독이 아니라, 이 순서로 바깥부터 채워 나간다.
8.1 연습: 이 레코드 정의를 읽어 보자
읽는 법 설명만으로는 몸에 익지 않으므로, 작은 레코드를 하나 놓아 둡니다. 먼저 스스로 답을 낸 뒤에 아래 해답을 보세요.
01 CUST-REC.
05 CUST-ID PIC X(8).
05 CUST-NAME PIC X(20).
05 CUST-KBN PIC X.
88 CUST-NORMAL VALUE '0'.
88 CUST-VIP VALUE '1'.
05 CUST-BALANCE PIC S9(7)V99 COMP-3.
05 CUST-HIST OCCURS 3 TIMES.
10 HIST-DATE PIC 9(8).
10 HIST-AMOUNT PIC S9(5)V99 COMP-3.
05 FILLER PIC X(4).
문제
CUST-REC는 전체 몇 바이트입니까.CUST-BALANCE는, 레코드 선두에서 세어 몇 바이트째부터 시작합니까.- 2번째
HIST-AMOUNT는, 선두에서 몇 바이트째부터 시작합니까. CUST-KBN에'1'이 들어 있을 때, 참이 되는 조건명은 무엇입니까.- 이 파일을 텍스트 에디터로 열었을 때, 망가져 보이는 것은 어느 항목입니까.
- 이 정의만으로는 알 수 없는 것을 2가지 드십시오.
해답
-
74바이트입니다. 내역은 이렇습니다.
항목 계산 바이트 수 CUST-IDX(8)8 CUST-NAMEX(20)20 CUST-KBNX1 CUST-BALANCE9자리 COMP-3 5 CUST-HIST(8 + 4)× 3회36 FILLERX(4)4 합계 74 88레벨은 조건명이므로 바이트를 소비하지 않습니다. 이것을 세어 버리는 것이 흔한 실수입니다.FILLER는 이름이 없을 뿐, 4바이트는 분명히 존재합니다. -
30바이트째부터입니다. 앞에
8 + 20 + 1 = 29바이트 있으므로, 그다음부터 시작합니다. -
55바이트째부터입니다.
CUST-HIST가 시작되는 것은 35바이트째이고, 1건이 12바이트이므로, 1번째는 35 - 46바이트, 2번째는 47 - 58바이트. 그중 선두 8바이트가HIST-DATE이므로,HIST-AMOUNT는 55바이트째부터입니다. -
CUST-VIP입니다.CUST-VIP라는 영역이 별도로 있는 것이 아니라,CUST-KBN이'1'일 때 그 이름으로 읽을 수 있다는 것뿐입니다. -
CUST-BALANCE와HIST-AMOUNT입니다. 둘 다COMP-3이므로, 문자로 읽으면 의미 없는 나열이 됩니다.HIST-DATE는PIC 9(8)의DISPLAY이므로, ASCII 환경이라면20260317처럼 숫자로 읽을 수 있습니다. 다만 EBCDIC 환경에서는, 숫자로 보이더라도 바이트값은 ASCII와 다릅니다. -
예를 들면 다음과 같은 것입니다.
- 이 정의 자체가
COPY로 들여온 것인지. copybook 쪽을 보지 않으면, 실제로 쓰이고 있는 버전인지 알 수 없습니다. - 파일의 문자 코드가 EBCDIC인지 ASCII인지.
PIC X와PIC 9 DISPLAY의 보이는 방식이 달라집니다. CUST-KBN에'0'과'1'이외가 들어가는 운용이 있는지. 조건명은 2개만 정의되어 있지만, 그 외의 값이 오지 않는다는 보장은 되지 않습니다.- 파일 자체의 속성(레코드 길이, 가변 길이 여부,
FILE STATUS). 이것은ENVIRONMENT DIVISION과FD를 보지 않으면 알 수 없습니다.
- 이 정의 자체가
문제 1과 3을 틀렸다면, 5.3의 자릿수와 바이트 수 표로 돌아가세요. COBOL 오독은, 대개 여기서부터 시작됩니다.
9. 흔히 막히는 지점
마지막으로, 초보자가 꽤 높은 확률로 걸리는 지점을 정리합니다.
REDEFINES를 “별도 변수”라고 생각한다
다릅니다. 같은 영역을 다른 형태로 읽고 있습니다. 한쪽을 고쳐 쓰면, 다른 한쪽의 보이는 방식도 바뀝니다.7
88을 “독립된 bool”이라고 생각한다
다릅니다.
직전 항목의 값에 이름이 붙어 있을 뿐입니다. SET WS-OK TO TRUE는 뒤에서 기저 항목에 대응값을 넣습니다.3
COPY를 무시하고 본문만 읽는다
열려 있는 파일은, 아직 전체의 절반입니다. 필드 정의, 공통 플래그, host variable이 통째로 바깥에 있는 것은 흔합니다.9
MOVE를 단순 대입이라고 생각한다
MOVE는 단순한 memcpy가 아닙니다.
받는 쪽의 형에 따라, 변환, 자릿수 맞춤, 0 채움, 잘라내기, 편집·역편집이 들어갈 수 있습니다.17
.의 영향을 가볍게 본다
COBOL의 .은 생각보다 무겁습니다.
명시 종단이 없는 오래된 코드에서는, 이 마침표가 어디까지를 닫고 있는지를 잘못 보면 제어 흐름을 잘못 읽습니다.12
packed decimal이나 EBCDIC을 “글자 깨짐”이라고 생각한다
망가졌다고는 할 수 없습니다. 애초에 문자열이 아니거나, ASCII가 아닐 뿐인 경우가 꽤 있습니다.46
OCCURS DEPENDING ON의 뒤쪽을 고정 위치라고 생각한다
가변 길이 테이블의 후속 항목은, 값에 따라 위치가 움직일 수 있습니다. 고정 길이라는 머리로 읽으면, 오프셋 계산이 전부 어긋납니다.8
10. 먼저 보는 조견표
| 발견한 단어 | 먼저 생각할 것 |
|---|---|
01 |
레코드나 그룹의 최상위. 여기서 전체상을 잡는다 |
88 |
플래그나 상태 코드의 의미명. 분기를 읽는 열쇠 |
PIC X(...) |
문자 항목 |
PIC 9(...) / S9(...)V... |
수치 항목. 자릿수와 소수 위치를 확인 |
COMP |
binary |
COMP-3 |
packed decimal. 금액·건수일 가능성이 높다 |
REDEFINES |
같은 영역을 다르게 해석하고 있다 |
OCCURS |
배열·table |
OCCURS DEPENDING ON |
가변 길이. 후속 위치에도 주의 |
FILLER |
이름은 없지만 길이는 있다 |
COPY |
copybook을 보지 않으면 완성형이 보이지 않는다 |
PERFORM |
주 경로의 골격 |
READ / WRITE / REWRITE |
파일 I/O |
EXEC SQL |
DB 처리 |
EXEC CICS |
트랜잭션 처리 |
FILE STATUS |
I/O의 결과 코드 |
11. 정리
COBOL은 오래되었기 때문에 어려운 것이 아닙니다. 데이터 정의, 외부 파일, 실행 문맥이 밀접하게 결합되어 있기 때문에 최초의 입구가 보이기 어려울 뿐입니다.
읽기 위한 최소 세트를 다시 한번 정리하면 이렇습니다.
DIVISION으로 지도를 잡는다DATA DIVISION을 먼저 읽는다PIC와USAGE로 항목의 형을 읽는다COMP-3,REDEFINES,OCCURS,88,COPY에 표시를 해둔다PERFORM,READ,WRITE,CALL을 따라간다FILE STATUS,EXEC SQL,EXEC CICS로 외부 경계를 짚는다.이 작용하는 방식을 가볍게 보지 않는다
여기가 보이면, COBOL은 “수수께끼의 고대 마법”에서 “레코드 처리의 언어”로 바뀝니다. 레거시 기술은, 이름이 오래되었기 때문에 무서운 것이 아니라, 처음에 보는 축척을 틀리면 갑자기 알기 어려워질 뿐입니다. 지도의 축척이 맞으면, 의외로 평범하게 읽을 수 있습니다.
flowchart TB
accTitle: 축척을 맞춰서 읽는다
accDescr: DIVISION으로 지도를 잡고, DATA DIVISION을 먼저 읽어서 항목의 형을 짚고, PERFORM이나 READ 등으로 흐름을 따라가고, FILE STATUS나 EXEC SQL이나 EXEC CICS로 외부 경계를 짚는 순서로 축척을 맞추면, COBOL은 레코드 처리의 언어로서 평범하게 읽힌다는 것을 보여주는 그림.
sm1["DIVISION으로 지도를 잡는다"] --> sm2["DATA DIVISION으로 형을 읽는다"]
sm2 --> sm3["PERFORM과 입출력으로 흐름을 따라간다"]
sm3 --> sm4["외부 경계를 짚는다"]
sm4 -.-> sm5["수수께끼의 고대 마법이 레코드 처리의 언어가 된다"]
그림 21: 어려움의 정체는 언어가 오래되어서가 아니라, 처음 축척을 고르는 방식이다.
12. 참고 자료
본문 중의 주요 참조처입니다. 본문의 위 첨자 번호가 그대로 이 목록으로의 링크가 되며, 각 항목 끝의 화살표에서 원래 위치로 돌아갈 수 있습니다.
-
IBM, “Reference format” / IBM, “Area A or Area B” / Micro Focus, “Fixed Format” ↩ ↩2 ↩3 ↩4
-
IBM, “Level-numbers” ↩ ↩2
-
IBM, “Format 2: condition-name value” ↩ ↩2
-
IBM, “Examples: numeric data and internal representation” ↩ ↩2 ↩3 ↩4
-
IBM, “PACKED-DECIMAL (COMP-3)” ↩ ↩2
-
IBM, “The EBCDIC character set” / IBM, “Handling differences in ASCII SBCS and EBCDIC SBCS characters” ↩ ↩2 ↩3 ↩4 ↩5
-
IBM, “REDEFINES 절” ↩ ↩2
-
IBM, “OCCURS DEPENDING ON clause” ↩ ↩2
-
IBM, “PERFORM statement” / IBM, “Procedure division structure” ↩
-
IBM, “Scope terminators” / IBM, “Coding a choice of actions” ↩ ↩2 ↩3 ↩4
-
IBM, “파일 구조를 상세하게 기술하기” ↩
-
IBM, “FILE STATUS clause” / IBM, “Using file status keys” ↩
-
IBM, “CICS 하에서 실행하는 COBOL 프로그램 코딩” ↩
-
IBM, “Computational items” ↩
-
IBM, “Elementary move rules” ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
MSMQ는 언제까지 쓸 수 있을까 ── 「비권장조차 아닌」 레거시 큐의 이전 판단
MSMQ는 공식 비권장 목록에 없지만, System.Messaging은 .NET Framework에만 존재해 .NET으로의 이전을 가로막습니다. 폐지설과 실제 현황을 사실 관계로 정리하고, 계속 사용할지 이전할지의 판단 기준과 이전 대상의 선택 ...
QR코드 판독값을 그대로 사용해서는 안 된다 ── 오류 정정이 통과해도 값은 보장되지 않는다
QR코드의 오류 정정은 정정이 통과한다고 해서 값이 올바르다고 보장하는 구조가 아닙니다. 샘플 이미지와 두 종류의 디코더를 이용한 실측을 바탕으로, 손상이 발생하는 위치에 따라 다른 값으로 읽혀 버리는 이유와 업무 시스템 측에 필요한 검증을 설명...
PowerShell로 REST API와 연동하기 ── Invoke-RestMethod의 실무
PowerShell에서 사내 API나 SaaS의 REST API를 호출하는 실무를 정리합니다. 인증 헤더를 넘기는 방법, 한국어(다국어) JSON의 문자 깨짐 대책, 4xx/5xx 에러 처리, 429 재시도, 페이징, 프록시와 TLS의 함정까지 ...
업무 시스템의 코드 설계 ── 상품 코드・고객 코드 정하는 방법과 체크 디지트
상품 코드・고객 코드 등 업무 시스템의 코드 체계를 정하는 실전 가이드. 유의미 코드와 무의미 일련번호의 판단표, JAN・Luhn 등 체크 디지트 산식과 C# 구현, Excel의 앞자리 0 소실 대책, 자릿수 초과와 이관까지 정리합니다.
DLL・COM 인터페이스의 하위 호환성 ── 어떤 변경이 호출 측을 망가뜨리는지의 판단표
DLL이나 COM 컴포넌트의 어떤 변경이 호출 측을 망가뜨리는지. 바이너리 호환・소스 호환・동작 호환의 3계층을 정리하고, 변경 내용별 판단표, COM 인터페이스 불변의 철칙, semver 운용까지를 실무 가이드로 정리합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
ActiveX 이관
COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
기존 자산 활용 & 이관 지원
COM / ActiveX / OCX 자산, 네이티브 코드, 32비트 의존성을 유지하면서 단계적인 이관 계획을 지원합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- COBOL 소스는 어디부터 읽기 시작하면 좋을까요?
- PROCEDURE DIVISION만 읽어서는 절반밖에 알 수 없습니다. COBOL은 로직의 언어이기 전에 꽤 강하게 레코드 정의의 언어이므로, 먼저 DATA DIVISION을 봅니다. 안전한 읽기 순서는, COPY를 전부 훑어서 copybook을 확인하고, 01 레벨의 레코드 정의를 목록화하고, PIC와 USAGE로 항목의 형을 읽고, READ·WRITE·CALL·EXEC SQL·EXEC CICS를 검색해서 입출력과 외부 경계를 파악한 뒤, PROCEDURE DIVISION 선두의 PERFORM 연쇄로 주 경로만 따라가는 흐름입니다.
- PIC S9(7)V99 COMP-3란 무슨 뜻인가요?
- PIC는 항목의 형을, USAGE는 어떤 표현으로 보유할지를 나타냅니다. S9(7)V99는 부호 있는 정수 7자리 + 소수 2자리의 수치이지만, V는 논리상의 소수점이며 데이터 위에 점 문자가 들어 있는 것은 아닙니다. COMP-3는 packed decimal로, 금액·세액·건수·레이트 계열의 항목에서 자주 나옵니다. 텍스트로 보면 망가져 보이는 것이 당연하므로, CSV나 UTF-8의 기분으로 덤프를 들여다보면 사고가 납니다.
- COBOL의 88 레벨과 REDEFINES는 어떻게 이해하면 될까요?
- 88은 독립된 bool 변수가 아니라, 직전 항목의 값에 이름을 붙인 조건명(condition-name)입니다. SET WS-OK TO TRUE는 뒤에서 기저 항목에 대응하는 값을 넣습니다. REDEFINES는 같은 메모리 영역을 다른 형태로 보는 구조로, 복사가 아니라 C 계열의 union에 가까운 감각입니다. 한쪽을 고쳐 쓰면 다른 한쪽의 보이는 방식도 바뀌기 때문에, 하나의 영역을 레코드 종류별로 구별하는 작성법에서 자주 나옵니다.
- COPY 문이 많아서 전체가 보이지 않을 때는 어떻게 하면 될까요?
- COPY는 컴파일 시점의 include이므로, 지금 열려 있는 소스는 아직 완성형이 아닐 가능성이 있습니다. 레코드 정의, 공통 플래그, SQL용 host variable, 외부 인터페이스가 copybook에 밀어넣어져 있는 것은 꽤 흔합니다. 읽기 어려울 때는 전개 후 소스나 compiler listing을 볼 수 없는지 확인하는 편이 빠르며, IBM Enterprise COBOL에는 라이브러리 처리 후의 입력 소스를 출력하는 MDECK라는 옵션도 있습니다.