자바와 C++의 비교

위키백과, 우리 모두의 백과사전.
이동: 둘러보기, 검색
프로그래밍 언어의 비교
일반
언어별

이 글은 자바C++ 프로그래밍 언어의 비교에 대한 글이다.

설계 목표[편집]

C++과 자바 언어의 차이는 각각의 탄생 역사에서부터 찾을 수 있다.

  • 자바는 처음에는 가전제품에 탑재되어 네트워크 컴퓨팅을 지원하기 위해서 만들었다. 가상 머신위에서 실행되기 때문에 안전성을 가지며 또한 이식성이 높다. 하위 플랫폼을 완벽히 추상화시켜 주는 광대한 분량의 라이브러리를 가지고 있다. 자바는 C와 비슷한 문법을 사용할뿐 직접적인 호환성은 없다. 사용하기 편하고 많은 사람이 이해하기 쉬운 언어를 목표로 설계되었다.

두 언어는 개발의 목적이 다르기 때문에 결과적으로 서로 다른 원리, 방침, 설계에서 트레이드오프에 차이가 생겼다.

C++ Java
C 소스 코드와 하위 호환성 다른 언어와 소스코드 호환성은 없음
직접적인 시스템 라이브러리 호출 가능 Java Native Interface를 이용
저수준 시스템 접근 가능 안전하게 보호되는 가상 머신위에서 실행됨
선택적 자동 경계 검사 항상 자동 경계 검사함
부호없는(unsigned) 연산 지원 부호없는 연산 지원 안함
값에 의한 매개변수 전달 또는 참조에 의한 매개변수 전달 항상 값에 의한 매개변수 전달. 매개변수로 객체에 대한 참조값을 사용할 수는 있다. 참조 대상의 내용을 변경할 수는 있지만, 참조값 자체는 변경할 수 없다; 메서드 호출 후에도 참조하는 객체는 다른 객체로 바뀌지 않을 것이다.
명시적 메모리 관리, 가비지 콜렉션은 추가적으로 라이브러리를 이용해야 함 항상 자동 가비지 콜렉션
명시적인 자료형 재정의 허용 자료형 안전성에 엄격함
C++ 표준 라이브러리는 적절한 범위까지 지원함 광대한 분량의 라이브러리
연산자 오버로딩 연산자는 재정의 할 수 없음

C++는 강력하지만 복잡하고 어려운 언어로, 성능 위주의 응용 프로그램이나 라이브러리에 적당하다. 자바는 대개 배우기 쉽지만, 플랫폼 자체가 가지는 전체 기능 이용이나 완벽한 성능 활용을 기대하기는 어렵다.

언어의 특징[편집]

문법[편집]

  • 자바 문법은 간단한 LALR 파서가 해석할 수 있는 문맥 자유 문법이다. C++ 해석은 약간 더 복잡하다. 예를 들면, Foo<1>(3);인 문장을 해석할 때 Foo가 변수라면 연속된 비교문이지만, Foo가 클래스 템플릿 이름이라면 객체를 생성하게 된다.
  • C++은 이름 공간 레벨에 상수, 변수, 함수가 있을 수 있다. 자바에서는 모든 상수, 변수, 함수는 반드시 클래스나 인터페이스에 속해 있어야 한다.
  • C++에 있는 const는 개념적으로 '읽기 전용' 데이터임을 명시하며 자료형에 적용된다. 자바에 있는 final은 변수가 다시 할당될 수 없음을 나타낸다. 기본 자료형에 대해서는 const intfinal int처럼 동일하지만, 복잡한 클래스에서는 조금 다르다:
C++ Java
const Rectangle r;
final Rectangle r = new Rectangle();
r = anotherRectangle; // 잘못
r = anotherRectangle; // 잘못
r.x = 5; // 잘못, r은 상수 Rectangle
r.x = 5; // 올바름, r은 여전히 같은 Rectangle 객체를 참조하고 있다.
  • C++은 goto을 지원한다. 자바는 코드를 읽기 쉽도록 구조적 제어 흐름을 강요한다. goto 문을 지원하지 않지만, goto와 비슷한 기능을 하는 레이블 break 와 레이블 continue를 제공한다.
  • C++은 자바에는 없는 저수준 기능을 제공한다. C++에서는, 포인터를 이용하면 저수준으로 운영 체제 컴포넌트에 쓰기 작업을 할 수밖에 없는 경우 특정한 메모리 위치에서 데이터를 조작하는 데 사용될 수 있다. 또한, 많은 C++ 컴파일러는 어셈블리어를 지원한다. 자바에서는, 그런 코드는 모두 외부 라이브러리에 배치해야하며 상당한 오버헤드를 가지는 Java Native Interface를 통해서만 접근이 가능하다.

의미론(Semantics)[편집]

  • C++은 기본 자료형 사이에 암시적 형변환을 허용하며, 사용자 정의 자료형에 대한 암시적 형변환도 가능하다. 자바에서는 기본 자료형 사이에 오직 넓은 범위로의 암시적 형변환을 허용하며, 다른 경우는 모두 cast를 통한 명시적 형변환만 가능하다.
    • 이런 영향은 불린 자료형이 필요한 조건문(if, while 그리고 for의 탈출 조건)에도 나타난다. 자바는 int를 boolean으로 좁힐 수 있는 암시적 형변환이 안되기 때문에 if(a = 5)와 같은 코드는 컴파일 오류가 발생한다. 요즘의 C++ 컴파일러는 대부분 경고를 발생한다.
  • 함수에 인자를 전달할 때, C++은 참조 호출값 호출 방식을 모두 지원한다. 자바에서는 인자는 항상 값 호출로 전달된다. [1]
  • 자바의 내장 자료형은 가상 머신이 결정한 일정한 크기와 범위를 가진다. 반면 C++의 내장 자료형은 최소한의 범위는 결정되어 있지만, 크기와 범위에 대한 정확한 표현은 작동하는 플랫폼의 지원에 따라 달라질 수 있다.
    • 예를 들어, 자바의 문자형(char)은 16비트 유니코드 방식이고 문자열은 이런 문자형의 연속으로 이루어진다. C++은 반각 문자와 전각 문자를 모두 지원하지만 이런 문자형의 실제 크기는 사용되는 플랫폼에 종속적이다.
  • 부동 소수점 연산에 있어서 C++은 플랫폼 종속적이다. 자바는 각기 다른 플랫폼에서 같은 결과를 보장한다. 하지만 수행 성능의 저하가 있을 수 있다.
  • C++의 포인터는 메모리 주소 값으로 직접 조작할 수 있다. 자바에는 포인터가 없다 — 객체에 대한 참조와 배열 참조가 있지만, 메모리 주소에 대한 직접 접근은 허용되지 않는다. C++은 포인터에 대한 포인터를 만들 수도 있지만, 자바의 참조는 객체에 대한 접근 기능만 제공한다.
  • C++의 포인터는 함수나 메서드를 가리킬 수 있다(함수 포인터펑터). 자바에서는 같은 메커니즘으로 객체에 대한 참조나 인터페이스에 대한 참조를 이용한다.
  • C++은 RAII 자원관리를 사용할 수 있다. 이 기술은 객체의 소멸에 맞추어 자동으로 메모리와 시스템 자원을 관리해 준다. 또, 가비지 콜렉션이 자동으로 메모리를 관리한다. 하지만 메모리를 제외한 다른 시스템 자원(윈도, 포트, 스레드)에 대해서는 사용이 끝나면 명시적 해제가 필요하다.
  • C++은 프로그래머가 연산자 오버로딩을 할 수 있다. 자바는 문자열 연결에 쓰이는 "+"와 "+="만 오버로딩되어 있을 뿐이다.
  • 자바는 리플렉션동적 로딩을 지원하는 표준 API를 가지고 있다.
  • 자바는 제네릭 프로그래밍을 지원하는 제네릭형을 가진다. C++은 템플릿을 가지고 있다. 템플릿은 더 광범위한 지원을 해준다.
  • 자바와 C++ 모두 기본 자료형(원시 자료형이나 내장 자료형으로도 불림)과 사용자 정의 자료형(복합 형)를 구분한다. 자바에서는 기본 자료형은 값으로의 의미만 있고, 사용자 정의 자료형은 참조로의 의미만 있다. C++에서는 모든 자료형이 값으로의 의미를 가지지만, 어떠한 자료형에 대해서도 참조(포인터)를 만들 수 있으므로 참조를 통해 객체를 다룰 수 있게 된다.
  • C++은 클래스의 다중 상속을 지원한다. 자바에서는 한 클래스는 오직 하나의 클래스만 상속할 수 있지만 복수의 인터페이스를 구현할 수 있다(즉, 형태에 대한 다중 상속은 지원하지만 구현에 대해서 단일 상속만 가능하다).
  • 자바에서 인터페이스와 클래스는 명시적으로 구별되는 개념이다. C++에서 자바의 인터페이스와 같은 역할을 하도록 하려면 클래스에 다중 상속과 순수 가상 함수를 적용하면 된다.
  • 자바는 언어와 표준 라이브러리 차원에서 멀티스레드를 지원한다. 자바의 synchronized 키워드는 간단하고 안전한 뮤텍스를 지원한다. 반면 C++은 멀티스레드에 대한 일반적인 메모리 모델이 없으므로 라이브러리를 사용하여 비슷한 일을 할 수 있다.

자원 관리[편집]

  • 자바는 자동 가비지 콜렉션을 지원한다. C++의 메모리는 일반적으로 스마트 포인터가 관리한다. C++ 에서도 가비지 콜렉션을 사용할 수 있지만, 일반적으로 잘 사용하지 않는다.
  • C++은 임의의 블록 크기로 메모리를 할당할 수 있다. 자바는 객체를 생성하는 방식으로만 메모리를 할당할 수 있다. (자바에서 프로그래머는 임의의 크기로 메모리를 할당하기 위해 바이트 배열을 생성하면 된다. 자바에서 배열도 객체이다.)
  • 자바와 C++은 자원 관리에서 서로 다른 관습이 있다. 자바는 주로 가비지 콜렉션에 의지해 메모리의 재생만을 할 수 있어 다른 자원상에서는 최근의 장면이 될지 모른다. 하지만,C++은 주로 RAII (Resource Acquisition Is Initialization) 라는 관습에 의지한다. 이것은 두 언어간의 아래와 같은 여러가지 차이가 나타나고 있다.
    • C++에서는 복합형의 객체를 스택영역에 할당하여 영역을 벗어나면 소멸되는 방식으로 사용하기도 한다. 자바에서는 복합형은 항상 힙영역에만 할당되고 가비지 콜렉터가 수거한다.
    • C++에는 소멸자가 있다. 자바에는 종결자(Finalizer)가 있다. 둘 다 객체가 소멸되기 직전에 호출되지만, 확연한 차이가 있다. C++의 객체 소멸자는 암시적으로(스택영역 변수의 경우) 또는 명시적으로 객체를 할당 해제할 때 실행된다. 소멸자는 객체가 할당 해제될 때 동기적으로 실행된다. 자바에서 객체의 할당 해제는 가비지 콜렉터가 암시적으로 처리한다. 자바의 객체 종결자는 마지막으로 그 객체에 접근한 시기보다 조금 후에 비동기적으로 호출된다. 대부분의 경우 종결자는 필요가 없다. 종결자는 할당 해제되기 전에 반드시 정리해야 할 자원(주로 JVM 외부 자원)이 있는 객체에 대해서만 필요할 뿐이다. 자바에서 안전한 동기적 자원 할당 해제를 하기 위해서는 명시적으로 try/finally를 사용하여야 한다.
    • C++에는 길잃은 포인터(dangling pointer, 이미 소멸된 객체를 가리키고 있는 포인터)가 문제가 될 수 있다. 만약 길잃은 포인터를 사용하려 한다면 프로그램은 오류가 발생하게 된다. 자바의 가비지 콜렉터는 참조중인 객체는 소멸시키지 않으므로 문제가 발생하지 않는다.
    • C++에서는 초기화하지 않고 객체를 생성할 수 있다(쓰레기 값을 가지고 있다). 자바는 기본 초기화가 강제로 수행된다(0 등으로 초기화 된다).
    • C++에는 메모리에 할당하였지만 접근 가능한 참조가 없는 객체가 있을 수 있다. 이런 접근 불가능한 객체는 소멸될 수도 없으므로 메모리 누수를 일으킨다. 반면, 자바에서는 접근 불가능한 객체가 되기 전까지 그 객체는 가비지 콜렉터가 소멸시키지 않는다. 자바의 가비지 콜렉션은 대부분의 메모리 누수를 예방하지만, 어떤 상황에서는 여전히 메모리 누수 문제가 발생할 수 있다.[2]
    • 자바는 메모리가 아닌 다른 자원의 누수에 대해서는 C++에 비해 상대적으로 취약하다.

라이브러리[편집]

  • 자바에는 C++에 비해서 상당히 거대한 표준 라이브러리가 있다. C++의 표준 라이브러리는 문자열, 컨테이너, 입출력 스트림 등의 비교적 범용적인 요소들만 제공한다. Java SE 표준 라이브러리는 컴퓨터 네트워크, 그래픽 사용자 인터페이스, XML 처리, 로깅, 데이터베이스 접근, 암호학, 기타 요소들을 모두 제공한다. 이런 추가 기능은 C++에서는 각자 구현할 필요없이 제3자(서드 파티) 라이브러리를 주로 이용한다.
  • C++은 C와 가장 하위 호환성이 좋은 언어이다. C 라이브러리에 있는 운영 체제API 같은 것을 C++에서는 직접 사용할 수 있다. 자바에서는 주로 플랫폼 종속적인 라이브러리로 가능한 여러 기능들을 크로스 플랫폼 환경에서 대부분 가능하도록 하는 풍부한 표준 라이브러리를 제공한다. 하지만 자바에서 운영 체제나 하드웨어 기능에 직접 접근하려면 Java Native Interface를 이용하여야만 한다.

런타임[편집]

  • C++은 보통 기계어로 직접 컴파일되고, 이를 운영 체제가 실행한다. 자바는 보통 바이트코드로 컴파일되고, 자바 가상 머신인터프리터 방식으로 실행하거나 JIT 컴파일러 방식으로 기계어로 컴파일한 다음 실행한다. 이론상 동적 재컴파일은 두 언어 모두에 적용할 수 있으며 특히 자바에 유용하다. 하지만 현재 동적 재컴파일은 거의 쓰이지 않게 되었다.
  • C++의 다소 자유로운 표현력(배열 범위 검사 없음, 미사용 포인터, 자료형 변환) 덕분에 컴파일시에 신뢰성 있는 검사가 안되고 런타임에 오류가 날 위험이 있다. 관련 오류로는 버퍼 오버플로, 세그먼테이션 폴트가 있다. 하지만 STL이 제공하는 고수준 추상 개념(벡터, 리스트, 맵)을 사용하면 오류를 피할 수 있다. 자바에서는 이런 오류는 아예 발생하지 않거나 자바 가상 머신에 적발되어 예외 처리 형태로 응용 프로그램에게 보고한다.
  • 자바는 배열의 범위를 벗어난 접근에 대해 명확하게 배열의 경계 검사를 요구한다. 이는 불안정성을 줄이기는 하지만 일반적으로 실행 속도에 나쁜 영향을 준다. 일부 경우, 컴파일러의 분석으로 이런 문제는 모두 제거되어서 경계 검사가 불필요한 일이 되기도 한다. C++은 배열의 범위를 벗어난 접근에 대해 아무런 행동도 하지 않으므로 배열의 경계 검사는 하지 않는다. C++ 표준 라이브러리의 벡터 같은 콜렉션의 경우 선택적으로 경계 검사를 제공한다. 요약하자면, 자바 배열은 "항상 안전하고, 엄격하게 검사하고, 가능하면 빠르게" 이고, C++ 배열은 "항상 빠르게, 전혀 검사하지 않고, 잠재적 위험이 있는" 것이다.

주석[편집]

  1. James Gosling, Bill Joy, Guy Steele, and Gilad Bracha, The Java language specification, third edition. Addison-Wesley, 2005. ISBN 0-321-24678-0 (see also online edition of the specification).
  2. "Java memory leaks -- Catch me if you can" by Satish Chandra Gupta, Rajeev Palanki, IBM DeveloperWorks, 16 Aug 2005