Void 타입
1. 개요
void 타입은 C/C++, Haskell, Java, .NET, ECMAScript 등 다양한 프로그래밍 언어에서 사용되는 개념이다. C와 C++에서는 함수의 반환 타입으로 사용되어 함수가 값을 반환하지 않음을 나타내며, void 포인터를 통해 임의의 형의 데이터를 가리킬 수 있다. Haskell에서는 거주자가 없는 빈 타입을 나타내며, void 타입으로의 함수는 결과를 반환하지 않는다. Java에서는 인스턴스를 생성할 수 없는 자리 표시자 클래스로, .NET에서는 System.Void 구조체로, ECMAScript에서는 undefined를 반환하는 연산자로 사용된다.
-
유형 이론 -
형 변환
형 변환은 프로그래밍에서 변수의 데이터 타입을 변경하는 것으로, 암시적 형 변환과 명시적 형 변환으로 나뉘며, 객체 지향 프로그래밍에서는 업캐스팅과 다운캐스팅이 발생하고, 각 언어는 고유한 규칙과 방법을 제공하며 잘못된 형 변환은 오류를 유발할 수 있다. -
유형 이론 -
대수적 자료형
대수적 자료형은 합 타입과 곱 타입을 조합하여 새로운 자료형을 정의하는 방법으로, 단일 연결 리스트나 이진 트리와 같은 자료 구조를 표현하고 패턴 매칭을 통해 자료형의 구조를 분해 및 처리하는 데 유용하며, 함수형 프로그래밍 언어에서 널리 사용된다. -
자료형 -
참조
참조는 프로그래밍에서 메모리 주소나 다른 데이터를 가리키는 값으로, 데이터의 효율적인 전달과 공유를 위해 사용되며, 포인터, 파일 핸들, URL 등이 그 예시이다. -
자료형 -
익명 함수
익명 함수는 이름이 없는 함수로, 람다 추상, 람다 함수, 람다 표현식, 화살표 함수 등으로 불리며, 함수형 프로그래밍 언어에서 람다식 형태로 많이 사용되고 고차 함수의 인수, 클로저, 커링 등에 활용되지만, 재귀 호출의 어려움이나 기능 제한과 같은 단점도 존재한다.
2. 역사적 배경
ALGOL 68의 `proc`(프로시저) 개념에서 영향을 받아, C 언어는 유의미한 값을 반환하지 않는 서브루틴(함수)의 반환형으로 `void`를 도입했다. 또한 함수 프로토타입의 매개변수 목록에 `void`를 사용하여 해당 함수가 매개변수를 받지 않음을 명시적으로 나타낼 수 있게 되었다.
초기 C 언어에서는 함수가 반환형을 명시하지 않으면 기본적으로 `int` 형으로 간주했으며, 매개변수가 없는 함수는 단순히 빈 괄호 `()`를 사용했다. 특정 타입이 정해지지 않은 데이터를 가리키는 포인터는 `char`나 `int`에 대한 포인터로 선언되기도 했다. 일부 초기 컴파일러는 함수의 반환값을 사용하지 않는 코드에 대해 경고를 발생시켰는데, 이를 피하기 위해 함수 호출 결과를 `void`로 캐스트하는 방식(예: `(void)printf("Hello");`)이 사용되기도 했다.
비야네 스트롭스트룹이 1979년에서 1980년경 C++ 개발을 시작했을 당시, `void` 타입과 `void` 포인터는 이미 AT&T에서 파생된 컴파일러가 지원하는 C 언어 방언의 일부였다.
시간이 흐르면서 C 언어 표준에도 변화가 있었다. 함수 반환형을 명시하지 않으면 `int`로 간주하는 규칙은 ANSI C (C89) 및 ISO/IEC 9899:1990 (C90)에서 표준화되었으나, C99 규격에서는 이 내용이 삭제되었다. 현재 이러한 코드는 대부분의 컴파일러에서 경고를 발생시킨다.
함수 프로토타입에서 매개변수 목록을 어떻게 쓰는지에 따라 C와 C++에서 의미가 달랐던 부분도 있다.
| 코드 | C에서의 의미 | C++에서의 의미 | 비고 |
|---|---|---|---|
void f(void); | 인수 없음 | 인수 없음 | C++에서는 이 표기법보다 아래의 void f();가 더 선호된다. |
void f(); | 인수의 개수나 타입을 명시하지 않음 (알 수 없는 개수의 인수를 받음) (C99부터 사용이 권장되지 않음) (C23부터 '인수 없음'으로 의미 변경) | 인수 없음 (선호되는 표기법) | 과거 C와 C++에서 의미가 달랐으나, C23 표준부터 통일되었다. |
이처럼 C 언어에서 `void f()` 형태의 선언은 원래 매개변수 정보를 생략하는 방식이었으나, C99에서 비권장되었고 C23에서는 C++와 동일하게 매개변수가 없음을 명시하는 방식으로 의미가 변경되었다.
3. C/C++ 에서
C와 C++에서 `void`는 주로 두 가지 목적으로 사용된다. 첫째, 함수가 어떤 값도 반환하지 않음을 나타내기 위해 함수의 반환형으로 사용된다. 이 경우 함수는 코드 블록의 끝에 도달하거나, 값을 명시하지 않은 `return 문`을 실행하여 종료된다. 둘째, 함수가 어떠한 매개변수도 받지 않음을 명시하기 위해 함수 프로토타입의 매개변수 목록에 사용될 수 있다.
함수 프로토타입에서 `void`를 명시적으로 사용하는 것과 매개변수 목록을 비워두는 것은 C와 C++에서 다른 의미를 가진다.
| 코드 | C에서의 의미 | C++에서의 의미 | 비고 |
|---|---|---|---|
| void f(void); | 인수 없음 | 인수 없음 | C++에서는 이 표기법은 권장되지 않는다 (C 링키지를 제외). |
| void f(); | 상수이지만 알 수 없는 수의 인수를 허용 (C99부터 비권장, C23부터 '인수 없음'으로 변경) | 인수 없음 | C++에서는 이 표기법이 선호된다. |
C 언어의 초기 버전에서는 반환형이 명시되지 않은 함수는 기본적으로 `int`형으로 간주되었고, 인수가 없는 함수는 단순히 빈 괄호 `()`를 사용했다. 당시 일부 컴파일러는 함수의 반환값을 사용하지 않는 모든 함수 호출에 대해 경고를 발생시켰는데, 이를 억제하기 위해 함수 호출을 `(void)`로 형 변환하는 코드가 사용되기도 했다. 비야네 스트롭스트룹이 C++ 개발을 시작하던 1979-1980년경, `void` 타입과 `void` 포인터는 AT&T에서 파생된 C 컴파일러 방언의 일부로 이미 존재했다.
C에서 매개변수 목록을 비워두는 것(void f();)은 오랫동안 "알 수 없는 개수의 인수를 받는다"는 의미였으나, 이러한 방식은 C99 표준부터는 사용하지 않는 것이 권장되었다. C23 표준부터는 C에서도 C++와 마찬가지로 빈 괄호 `()`가 매개변수가 없음을 명시적으로 나타내게 되었다.
3.1. void 포인터
C 언어와 C++에서는 void형의 포인터가 있으며, void *와 같이 표기한다. 이는 "특정되지 않은"(임의의) 형의 데이터를 가리키는 포인터이다. 즉, 이 문맥에서 void는 범용 포인터로 취급된다. 프로그램에서 어떤 형의 데이터도 void *로 가리킬 수 있으며, 반대로 원래 데이터를 참조할 수도 있다. 이러한 특징 때문에 콜백 함수의 인수로 임의의 사용자 정의 데이터의 주소를 전달하여 처리하는 것과 같은 다형적인 함수를 작성할 때 유용하다.
ISO C 표준 규격에서는 함수 포인터에 대한 취급이 다르며, void *와의 상호 변환을 보장하지 않는다. 그러나 POSIX의 dlsym() 함수는 dlopen()으로 동적으로 로드한 모듈에서 변수나 함수의 심볼 주소를 void *로 취득할 수 있는데, 이 동작은 POSIX를 준수하는 구현이 void *에서 함수 포인터로의 변환을 정상적으로 실행할 수 있다는 것을 전제로 하고 있다.
4. 하스켈(Haskell)에서
함수형 프로그래밍 언어인 Haskell에서 void 타입은 거주자가 없는 빈 타입을 나타낸다[https://hackage.haskell.org/package/void]. void 타입으로의 함수는 결과를 반환하지 않으며, 타입 시그니처가 `IO Void`인 부작용을 가진 프로그램은 종료되지 않거나 충돌한다. 특히, void 타입으로의 전체 함수는 없다.
5. 자바(Java)에서
자바에는 `java.lang.Void` 클래스가 있다. 이는 인스턴스를 생성할 수 없는 자리 표시자 클래스이며, 키워드 `void`를 나타내는 `java.lang.Class` 객체에 대한 참조를 유지하는 데 사용된다.
6. 닷넷(.NET)에서
.NET에는 System.Void 구조체가 있다. C#의 경우 void 키워드로 매핑된다.