현재 선택한 정보처리 과정

정보처리기사 필기 이론 학습

이론 목록으로 돌아가기

언어 실행 방식·자료형·연산·제어 흐름

언어의 실행 방식과 프로그래밍 방식은 서로 다른 분류이다. 자료형·변수·형 변환·연산자·제어문을 구분하고 C·Java·Python의 짧은 코드를 언어별 규칙에 따라 추적한다.

예상 읽기 19

언어의 실행 방식과 프로그래밍 방식은 서로 다른 분류이다. 자료형·변수·형 변환·연산자·제어문을 구분하고 C·Java·Python의 짧은 코드를 언어별 규칙에 따라 추적한다.

그림으로 확인하기

좌우로 이동해 그림을 확인하세요.그림 크게 보기
C에서 &&는 왼쪽이 거짓이면, ||는 왼쪽이 참이면 오른쪽을 평가하지 않는다.
C에서 &&는 왼쪽이 거짓이면, ||는 왼쪽이 참이면 오른쪽을 평가하지 않는다.

프로그래밍 언어의 분류

분류주된 관점대표 예
절차적 언어처리 순서와 절차·함수를 중심으로 프로그램 구성C, Pascal, FORTRAN
객체지향 언어데이터와 동작을 객체로 묶고 상속·다형성 등 활용Java, C++
스크립트 언어실행 환경에서 작업을 해석·실행하고 자동화·연결에 활용Python, JavaScript, 셸 스크립트
선언형 언어세부 실행 절차보다 원하는 결과·조건·관계를 표현SQL, Prolog

명령형은 어떻게 처리할지를 명령의 순서로 기술하고, 선언형은 무엇을 얻고 어떤 조건이 성립해야 하는지를 기술한다. SQL로 조건에 맞는 자료를 요청할 때 사용자가 모든 레코드 탐색 절차를 직접 지정하지 않는 것이 선언형의 예이다.

함수형 언어는 함수의 적용과 값 계산을, 논리형 언어는 사실·규칙과 논리적 추론을 중심으로 한다. Lisp·Haskell은 함수형, Prolog는 논리형의 대표 예로 구분한다. 실제 언어는 여러 방식을 함께 지원할 수 있으므로 이 분류가 항상 서로 배타적인 것은 아니다.

객체지향의 기본은 캡슐화(데이터와 동작을 묶고 내부 구현을 감춤), 상속(기존 타입의 특성을 이어받음), 다형성(공통 인터페이스로 서로 다른 동작을 사용함), 추상화(핵심 성질을 모델링함)이다. 객체지향이라고 인터프리터만 사용하는 것은 아니며, 선언형이라고 실행 비용이 항상 더 작은 것도 아니다.

소스 코드가 실행되기까지

컴파일·인터프리트·바이트코드

  • 컴파일(compile): 소스 코드를 실행 전에 또는 실행 과정 중 다른 형태의 코드로 번역한다. 결과가 반드시 CPU 기계어일 필요는 없으며, Java 바이트코드처럼 가상 머신용 중간 코드일 수도 있다.
  • 인터프리트(interpret): 인터프리터가 소스 코드나 중간 코드를 읽어 의미를 해석하며 실행한다.
  • 바이트코드(bytecode): 특정 가상 머신이 이해하도록 설계한 중간 명령 형식이다. 바이트코드만으로 실행되는 것이 아니라 호환되는 실행 환경이 필요하다.
  • 가상 머신(VM): 중간 코드를 실행하는 추상 실행 환경이다. 실제 하드웨어와 운영체제 차이를 감추는 역할을 할 수 있다.
  • JIT 컴파일(Just-In-Time compilation): 실행 중 자주 수행되는 코드 등을 기계어로 변환하여 재사용하는 방식이다. 가상 머신의 가능한 구현 전략이지 모든 가상 머신이 반드시 같은 방식으로 수행한다는 뜻은 아니다.

판별 기준: 언어를 “컴파일 언어”와 “인터프리터 언어”로 고정 분류하기보다 소스 → 중간 결과 → 실행 주체를 확인한다.

C·Java·Python의 대표 실행 경로

구분대표 경로시험에서 확인할 점
C소스 → 전처리 → 컴파일 → 어셈블 → 목적 파일 → 링크 → 실행 파일컴파일 성공과 링크 성공은 다르다. 헤더 선언이 있어도 함수 정의를 찾지 못하면 링크 오류가 날 수 있다.
Java.java 소스 → 컴파일 → .class 바이트코드 → JVM이 필요한 클래스를 로딩·링크하고 정해진 시점에 초기화하여 실행바이트코드는 곧바로 특정 CPU의 기계어인 것이 아니다. 해석·JIT 등의 내부 실행 방식은 JVM 구현이 선택할 수 있다.
Python소스 → 구문 분석·컴파일 → 실행Python 언어 자체가 특정 바이트코드 형식을 보장하는 것은 아니다. 대표 구현인 CPython은 소스를 코드 객체와 바이트코드로 컴파일한 뒤 가상 머신에서 실행한다.

전처리·컴파일·어셈블·링크의 개별 역할은 개발 환경 이론에서 확인한다.

오류가 드러나는 시점

시점대표 원인예시
구문 분석·컴파일문법 오류, 선언·형 규칙 위반Java에서 boolean이 필요한 조건에 정수를 사용함
링크·로딩필요한 함수 정의, 클래스, 라이브러리를 찾지 못함C에서 선언된 외부 함수의 구현을 링크하지 않음
실행실제 입력과 실행 경로에서 발생한 오류Python에서 실행된 분기가 정의되지 않은 이름을 참조함
  • 컴파일에 성공해도 배열 범위 위반, 실행 시 형 검사 실패, 0으로 나누기 같은 실행 중 문제가 남을 수 있다.
  • 인터프리터를 사용하는 구현도 실행 전에 구문 분석이나 중간 코드 생성을 수행할 수 있다.
  • 실행되지 않은 분기의 이름 오류는 늦게 나타날 수 있지만, 파일 전체의 문법 오류는 실행 전에 발견될 수 있다.

변수·이름·값·자료형

자료형이 정하는 것

자료형(type)은 보통 다음을 정한다.

  1. 표현할 수 있는 값의 집합과 범위
  2. 메모리 표현과 필요한 저장 공간의 제약
  3. 적용할 수 있는 연산
  4. 연산 결과의 형과 변환 규칙

“변수는 값을 담는 상자”라는 설명은 입문용 비유일 뿐 모든 언어에 그대로 적용되지 않는다. C·Java의 변수와 Python의 이름 결합은 구분해야 한다.

언어변수·이름의 관점타입 검사 방식
C선언된 형을 가진 객체를 식별하는 이름컴파일 시점에 식의 형을 주로 검사하는 정적 타입
Java기본형 값 또는 객체를 가리키는 참조값을 저장하는 변수정적 타입. var를 사용해도 컴파일러가 형을 추론할 뿐 동적 타입이 되는 것은 아님
Python이름이 객체에 결합되며, 객체가 자신의 형을 가짐실행 중 객체의 형에 따라 연산을 검사하는 동적 타입

정적 타입이라고 실행 중 형 검사가 전혀 없는 것은 아니다. Java의 다운캐스팅처럼 실행 시점 검사가 필요한 연산도 있다. 반대로 동적 타입이라고 타입이 없는 것도 아니다. Python의 모든 객체에는 형이 있으며 허용되지 않는 연산은 실행 중 TypeError가 될 수 있다.

C·Java·Python 정수형 비교

항목CJavaPython
정수 폭short, int, long 등의 정확한 비트 수는 구현에 따라 달라질 수 있음byte 8비트, short 16비트, int 32비트, long 64비트int는 언어 수준에서 고정 폭 정수형이 아니며 필요한 만큼 확장됨
부호char의 부호 여부는 구현에 따라 달라질 수 있음byte·short·int·long은 부호 있는 2의 보수, char는 16비트 부호 없는 UTF-16 코드 단위별도의 고정 폭 부호 없는 기본 정수형 없음
넘침부호 없는 정수는 형의 범위에 따라 모듈러 연산, 부호 있는 넘침은 정의되지 않은 동작이 될 수 있음정수 연산은 고정 폭 2의 보수 결과로 상위 비트가 버려질 수 있음정수 자체는 고정 폭 넘침 대신 더 큰 값을 표현하며 메모리 한계의 영향을 받음

추가로 기억할 점은 다음과 같다.

  • C에서 sizeof(char) == 1은 항상 참이지만, C의 1바이트가 반드시 8비트라는 뜻은 아니다.
  • Java의 booleantruefalse만 가지며, 언어 명세가 변수 하나의 저장 크기를 1바이트로 정하지 않는다.
  • Java의 참조형 변수는 객체 자체가 아니라 객체를 가리키는 참조값 또는 null을 가진다.
  • Python에서 같은 이름에 정수 객체를 결합한 뒤 문자열 객체를 다시 결합할 수 있다. 이름의 결합 대상이 바뀐 것이지 객체의 형이 바뀐 것은 아니다.
PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
value = 10          # value는 int 객체를 참조
value = "ten"       # 이제 str 객체를 참조

형 변환과 정보 손실

자동 변환과 명시적 변환

  • 암시적 형 변환: 연산이나 대입 규칙에 따라 언어가 자동으로 형을 바꾼다.
  • 명시적 형 변환(캐스트): 개발자가 변환할 형을 코드에 직접 지정한다.
  • 파싱(parsing): 문자열의 문법을 해석하여 숫자 같은 다른 값을 만드는 과정이다. 수치형끼리의 캐스트와 같지 않으며 입력 형식이 잘못되면 실패할 수 있다.

형 변환 문제에서는 다음 순서로 판단한다.

  1. 변환 전 값과 형을 확인한다.
  2. 변환 후 형의 범위와 정밀도를 확인한다.
  3. 부호, 소수부, 상위 비트가 보존되는지 확인한다.
  4. 변환이 허용되는지와 값이 정확히 보존되는지를 따로 판단한다.

넓히기 변환도 항상 정확하지 않다

일반적으로 작은 범위에서 큰 범위로 옮기는 넓히기 변환은 범위 초과 위험이 작지만, 항상 모든 값을 정확히 보존한다는 뜻은 아니다. 예를 들어 Java의 intfloat로 넓히는 변환은 허용되지만 큰 정수의 일부 정밀도를 잃을 수 있다. int → long처럼 모든 원래 값을 정확히 표현하는 변환과 구분해야 한다.

C와 Java에서는 작은 정수형끼리 산술 연산할 때 먼저 정수 승격이 일어날 수 있다. Java에서 두 byte 변수의 a + b 결과 형은 int이므로, 결과를 byte 변수에 대입하려면 범위 확인과 명시적 변환이 필요하다.

좁히기 변환 예제

다음 Java 코드는 32비트 int 값 300을 8비트 byte로 좁힌다. 하위 8비트만 남아 결과는 44가 된다.

JAVA코드 영역 안에서 좌우로 이동할 수 있습니다.
public class Main {
    public static void main(String[] args) {
        byte value = (byte) 300;
        System.out.println(value);
    }
}

실행 결과:

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
44

명시적 캐스트를 사용했다는 사실은 “안전하다”는 보장이 아니다. 개발자가 정보 손실 가능성을 받아들였다는 뜻에 가깝다.

실수에서 정수로 변환

값이 변환 후 정수형 범위 안에 있다는 전제에서 C와 Java의 실수→정수 변환, Python의 int(실수)는 소수부를 0 방향으로 버린다.

결과
int(3.9)3
int(-3.9)-3

이를 수학의 내림과 혼동하면 안 된다. 내림은 -3.9-4로 만들지만 0 방향 절단은 -3으로 만든다. 범위를 벗어난 값이나 NaN·무한대의 처리 방식은 언어마다 다르므로 별도 규칙을 확인해야 한다.

정수 나눗셈의 언어 차이

계산C·JavaPython
양의 정수 7 / 322.333...
음수 포함 -7 / 3-2-2.333...
몫을 정수로 구함/가 0 방향으로 절단//가 음의 무한대 방향으로 내림
-73의 정수 몫-7 / 3 == -2-7 // 3 == -3
나머지-7 % 3 == -1-7 % 3 == 2

Python의 /는 피연산자가 정수여도 부동소수점 나눗셈 결과를 반환한다. Python의 //와 C·Java의 정수 /는 양수에서 같아 보이지만 음수가 포함되면 결과가 달라질 수 있다.

연산자와 식을 읽는 법

우선순위·결합 방향·평가 순서

세 용어는 서로 다르다.

  • 우선순위(precedence): 괄호가 없을 때 어떤 연산자와 피연산자가 먼저 묶이는지를 정한다.
  • 결합 방향(associativity): 같은 우선순위 연산자가 연속될 때 왼쪽 또는 오른쪽 중 어느 쪽부터 묶이는지를 정한다.
  • 평가 순서(evaluation order): 묶인 식의 피연산자나 함수 호출이 실제로 어느 순서에 실행되는지를 정한다.

예를 들어 2 + 3 * 4는 곱셈의 우선순위가 높으므로 2 + (3 * 4)로 묶여 14가 된다. C·Java의 a = b = 5는 대입 연산자가 오른쪽 결합이므로 a = (b = 5)로 묶인다. Python의 연쇄 대입은 같은 모양으로 값을 결합할 수 있지만 대입 연산자 식으로 해석하지 않는다.

그러나 우선순위가 실제 평가 순서를 모두 결정하지는 않는다.

CJava·Python
f() + g()f()g() 중 어느 호출이 먼저인지 이 식만으로 고정되지 않음일반적으로 피연산자를 왼쪽에서 오른쪽으로 평가

C에서 (f()) + (g())처럼 괄호를 추가해도 덧셈의 두 피연산자 중 어느 함수가 먼저 호출되는지까지 정하지는 않는다. 괄호는 묶임을 명확히 하지만 모든 언어에서 실행 순서를 강제하는 장치는 아니다.

주요 연산자 범주

범주대표 연산자판별 포인트
산술+ - * / %피연산자 형에 따라 정수·실수 나눗셈 결과가 달라짐
비교< <= > >= == !=대입 =과 동등 비교 ==를 구분
논리C·Java의 <code>&&</code>·<code>||</code>·<code>!</code>, Python의 and·or·not단락 평가 여부와 결과 형을 확인
비트<code>&</code>·<code>|</code>·<code>^</code>·<code>~</code>·<code><<</code>·<code>>></code>논리값이 아니라 정수의 각 비트를 대상으로 하는 경우가 기본
대입= += -= *=계산과 상태 변경이 함께 일어남
조건C·Java의 조건 ? 참값 : 거짓값조건에 따라 두 식 중 하나만 선택

단락 평가

단락 평가(short-circuit evaluation)는 앞 피연산자만으로 결과가 확정되면 뒤 피연산자를 평가하지 않는 방식이다.

  • C·Java: A && B에서 A가 거짓이면 B를 평가하지 않는다. A || B에서 A가 참이면 B를 평가하지 않는다.
  • Python: A and B, A or B도 단락 평가하지만 결과를 항상 True 또는 False로 바꾸지 않고 선택된 피연산자 자체를 반환한다.
PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
print(False and (1 / 0))
print("" or "기본값")
print(list(range(1, 6, 2)))

실행 결과:

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
False
기본값
[1, 3, 5]

첫 번째 식은 왼쪽이 거짓이므로 1 / 0을 실행하지 않는다. 두 번째 식은 빈 문자열이 거짓으로 판정되어 오른쪽 문자열을 반환한다. 세 번째 식은 range의 종료값 6을 포함하지 않는다.

논리 연산과 비트 연산

  • C의 &&, ||는 논리 연산이며 결과는 정수 0 또는 1이다.
  • Java의 &&, ||boolean 논리 연산이며 단락 평가한다.
  • C·Java의 정수 &, |, ^는 각 비트에 연산을 적용하며 단락 평가하지 않는다.
  • Java의 boolean에 사용하는 &, |도 논리 결과를 만들 수 있지만 양쪽 피연산자를 모두 평가한다.
  • Python의 and, or는 논리 선택 연산이고 &, |는 정수 비트 연산 또는 자료형이 정의한 별도 연산에 사용된다.

따라서 “&는 AND이므로 &&와 항상 같다”는 선지는 틀리다. 결과가 우연히 같더라도 피연산자 형과 평가 여부가 다를 수 있다.

조건문과 반복문

조건식의 언어 차이

언어조건으로 사용할 수 있는 대표 값
C스칼라 값. 0은 거짓, 0이 아닌 값은 참으로 판정
Javaboolean 식만 허용
Python객체의 참·거짓 판정 규칙 사용. 0, 빈 문자열, 빈 컬렉션 등은 보통 거짓

그러므로 if (1)은 C에서는 참인 조건이지만 Java에서는 boolean이 아니므로 허용되지 않는다. Python의 if 1:은 1이 참으로 판정되어 실행된다.

분기 사슬과 독립된 여러 조건문

if - else if - else 또는 Python의 if - elif - else는 위에서부터 조건을 검사하여 처음 참인 분기 하나만 실행한다. 반면 독립된 if가 여러 개면 각각의 조건을 모두 검사하므로 둘 이상이 실행될 수 있다.

PYTHON코드 영역 안에서 좌우로 이동할 수 있습니다.
x = 7
score = 0

if x >= 5:
    score += 1
elif x >= 7:
    score += 10

print(score)  # 1

두 번째 elif 조건도 수학적으로 참이지만 첫 번째 분기가 이미 선택되었으므로 실행되지 않는다. 두 문장이 모두 독립된 if였다면 결과는 11이다.

선택문에서의 fall-through

전통적인 C switch와 Java의 콜론형 switch 문은 일치한 case에서 시작한 뒤 break를 만나지 않으면 다음 case의 문장으로 이어질 수 있다. 이를 fall-through라고 한다.

  • break는 해당 switch를 빠져나간다.
  • Java의 화살표형 case ... -> 규칙은 전통적인 fall-through 방식과 다르다.
  • Python의 match는 패턴을 선택하는 문법이며 C의 fall-through 방식으로 실행되지 않는다.

반복문 비교

반복문조건 검사·진행 방식최소 실행 횟수
while본문 전에 조건 검사0회
do-while본문 실행 후 조건 검사1회
C·Java 기본 for초기화 1회 → 조건 → 본문 → 갱신 → 조건 반복조건에 따라 0회
Python for이터러블에서 원소를 하나씩 가져와 대상 이름에 결합원소가 없으면 0회

Python에는 C·Java와 같은 내장 do-while 문법이 없다. 필요하면 while True와 조건부 break로 같은 흐름을 구성할 수 있다.

range(start, stop, step)stop을 포함하지 않는다. 양의 step에서 마지막 값은 stop보다 작은 값이며, 음의 step에서는 stop보다 큰 값까지만 생성된다. step에 0은 사용할 수 없다.

break와 continue

  • break: C·Java에서는 가장 가까운 반복문 또는 switch를 종료하고, Python에서는 가장 가까운 반복문을 종료한다.
  • continue: 반복문 전체를 끝내지 않고 현재 회차의 남은 본문을 건너뛰어 다음 반복 판단으로 이동한다.

continue 이후의 이동 지점은 반복문 종류에 따라 다르다.

반복문continue 후 이동
C·Java 기본 for갱신식 실행 후 조건 재검사
while조건 재검사
Python for이터러블의 다음 원소를 가져옴

중첩 반복에서 일반적인 break는 가장 가까운 반복문 하나만 빠져나간다. Java에는 레이블을 이용한 별도 탈출 문법도 있지만, 레이블이 없다면 모든 중첩 반복을 한꺼번에 종료하지 않는다.

Python 반복문의 elsebreak 없이 반복이 정상 종료되었을 때 실행된다. 반복 횟수가 0회여도 break가 없었으므로 실행될 수 있으며, continue만 사용한 것은 else 실행을 막지 않는다.

C 정수 리터럴과 비트 계산

C17에서 12는 10진 정수, 앞에 0을 붙인 014는 8진 정수, 0x0C는 16진 정수이며 세 값은 모두 12이다. u 접미사는 unsigned 계열 정수 리터럴에 사용한다. 8진수에는 숫자 8과 9를 쓰지 않는다.

정수의 비트 연산은 같은 위치의 비트끼리 수행한다. 아래 표의 기호는 비트 AND·OR·XOR이다.

왼쪽 비트오른쪽 비트AND &ORXOR ^
00000
01011
10011
11110

OR의 C 연산자는 |이다. ~는 각 비트를 반전한다. 비트 폭을 정하지 않은 채 ~x의 모든 상위 비트를 생략해서는 안 된다. 특정 비트를 검사하려면 x & mask, 켜려면 x | mask, 끄려면 x & ~mask, 뒤집으려면 x ^ mask를 사용할 수 있다. 같은 마스크로 XOR를 두 번 적용하면 원래 비트가 된다.

unsigned 값의 x << n은 왼쪽으로 n비트 이동하고 아래쪽을 0으로 채운다. 표현 범위 안에서는 x * 2^n과 같다. unsigned x >> n은 오른쪽으로 이동하고 위쪽을 0으로 채우므로 정수 몫 x / 2^n에 해당한다. 이동 횟수는 음수가 아니며 정수 승격 후 왼쪽 피연산자의 비트 폭보다 작아야 한다. signed 음수의 시프트나 signed 표현 범위를 넘는 왼쪽 이동을 단순한 곱셈·나눗셈으로 일반화하지 않는다.

예를 들어 10u ^ 3u는 이진수 1010 ^ 0011 = 1001이므로 9이다. 이 연산을 덧셈이나 OR로 바꾸면 같은 결과를 보장하지 않는다.

식의 경계와 평가 횟수

C의 덧셈은 시프트보다 우선순위가 높으므로 a << b + ca << (b + c)로 묶인다. 비교 결과를 다시 비교하는 a < b < c는 수학의 연쇄 부등식이 아니라 (a < b) < c이다. 관계·동등 비교와 논리 연산은 참이면 1, 거짓이면 0을 만든다. 비트 연산은 이처럼 결과를 0·1로 정규화하지 않는다.

조건 ? 식1 : 식2는 조건을 평가한 뒤 두 식 중 선택한 하나만 평가한다. 쉼표 연산자 (식1, 식2)는 식1을 먼저 평가한 뒤 식2를 평가하고, 전체 식의 값은 식2의 값이다. 함수 인자를 구분하는 쉼표는 이 순서를 보장하는 쉼표 연산자가 아니다.

가변 길이 배열이 아닌 형·객체에 대한 sizeof는 바이트 수를 구하며 피연산자 식을 실행하지 않는다. 예를 들어 일반 int x에 대한 sizeof(x++)를 평가했다고 x가 증가하지 않는다. 가변 길이 배열과 관련된 예외를 모든 sizeof에 적용하지 않는다.

중괄호 없이 if문을 중첩했을 때 else는 아직 else가 연결되지 않은 가장 가까운 if에 연결된다. 들여쓰기만으로 연결 대상을 바꿀 수는 없다.

for문의 continue는 본문 나머지를 건너뛰고 증감식으로 이동한다. do-whilecontinue는 조건식 평가로 이동한다. 중첩 반복문에서 break는 가장 안쪽 반복문 또는 switch를 종료한다. 빈 문장 ;도 반복문의 본문이 될 수 있으므로 중괄호와 세미콜론을 함께 확인한다.