7. Basic Types(2)

하모씨·2021년 10월 16일
0

KNKsummary

목록 보기
5/23

4. Type Conversion

수학에 관해서는 C언어보다 컴퓨터가 더 제한적인 경향이 있다.
컴퓨터와 다르게 C언어는 표현식에서 기본 자료형들을 섞어서 쓰는 것을 허용한다. 정수들과 부동소수점숫자들과 심지어는 문자들까지도 하나의 표현식에 결합할 수 있다. C 컴파일러는 하드웨어가 표현식을 계산할 수 있도록 몇몇 피연산자들을 다른 자료형으로 변환시켜야 할 수도 있다. 예를 들면 16비트의 short와 32비트의 int를 더했을 때, 컴파일러는 short 값을 32비트로 변환시킨다. intfloat를 더하면 intfloat 서식(format)으로 변환시킨다.
컴파일러가 이러한 변환을 프로그래머의 참여 없이 자동적으로 조절하기 때문에 암시적 변환(implicit conversion)이라고 부른다.
C언어는 cast operater를 통하여 프로그래머가 수행할 수 있는 명시적 변환(explicit conversion)또한 제공한다.
암시적 변환(implicit conversion)은 아래와 같은 상황에서 수행된다.

  • 수학 또는 논리 표현식의 피연산자가 같은 자료형이 아닐 때(C언어는 usual arithmetic conversion이라고 알려진 것을 수행한다)
  • 대입의 오른쪽 표현식의 자료형과 왼쪽에 있는 변수의 자료형이 같지 않을 때
  • 함수 호출에서의 매겨변수의 자료형이 대응하는 파라미터의 자료형과 일치하지 않을때
  • return 구문 내부의 표현식의 자료형이 함수의 return 자료형과 일치하지 않을 때

The Usual Arithmetic Conversions

usual arithmetic conversions는 수학, 관계, 동등(==) 연산자를 포함한 대부분의 이항 연산자의 피연산자에 적용된다.
예를 들어 f의 자료형이 float이고 i의 자료형이 int라고 했을 때, f + i의 표현식이 있다면 usual arithmetic conversion이 일어난다. 왜냐하면 자료형이 같지 않기 때문이다. 또, 명확하게 ifloat로 만드는 것이 더 안전하다. 정수는 항상 float로 변환될 수 있는데, 일어날 수 있는 최악의 경우는 정확도의 아주 일부를 잃는다는 것이다.
부동소수점 숫자를 int로 변환하는 것은 숫자의 분수(fractional) 부분이 대가를 치르게 된다. 더 나쁘게, 만약 원래의 숫자가 가능한 가장 큰 정수보다 커지거나 가능한 가장 작은 정수보다 작아진다면 완벽하게 의미없는 결과를 얻을 것이다.

usual arithmetic conversion의 전략은 두 피연산자를 안전하게 수용할 수 있는 "가장 좁은(narrowest)" 자료형으로 변환하는 것이다(대략적으로 말하면 여기서 좁다는 의미는 저장하는 것에 더 적은 byte가 필요한 경우 더 좁다). 피연산자의 자료형은 더 좁은 자료형을 다른 자료형으로 변환하여 일치하도록 만들어진다. 이러한 행동은 promotion으로 알려져 있다.
가장 일반적인 promotion은 integral promotion인데, 이는 character 또는 short integer를 int로(또는 일부의 경우에 unsigned int로) 바꾸도록 하는 것이다.

우리는 usual arithmetic conversion을 수행하는 규칙을 두 가지 경우로 쪼갤 수 있다.

  • 각각의 자료형이 전부 부동소수점 자료형일 때 : 더 작은 자료형의 피연산자가 아래의 다이어그램을 따라 promote한다.
long doubledoublefloat

만약 하나의 피연산자가 long double의 자료형을 가질 경우에 다른 피연산자도 long double자료형이 된다.
만약 하나의 피연산자가 double의 자료형을 가질 경우에는 다른 피연산자도 double자료형이 된다.
만약 하나의 피연산자가 float의 자료형을 가질 경우에는 다른 피연산자도 float자료형이 된다.
이 규칙은 정수와 부동소수점 자료형의 혼합에도 적용된다. 만약 피연산자가 long int고 다른 피연산자가 double이라면 long int 피연산자는 double이 된다.

  • 각각의 자료형이 둘다 부동소수점 자료형이 아닐 때 : 첫번째로 integral promotion을 각각의 피연산자에 수행한다(두 쪽의 피연산자가 모두 character나 short integer가 아니게 하기 위해서). 그 후 더 작은 자료형의 피연산자가 아래의 다이어그램을 따라 promote한다.
unsigned long intlong intunsigned intint

특별한 하나의 경우가 있는데, 이는 오직 long intunsigned int가 같은 길이를 가질 때에만 발생한다. 이 환경에서는 하나의 피연산자가 long int 이고 다른 피연산자가 unsigned int일 때에, 각각은 모두 unsigned long int로 변환된다.

signed 피연산자가 unsigned 피연산자가 결합될 때 signed 피연산자가 unsigned 값으로 변환된다. 이 변환은 n+1을 더하거나 빼는 과정이 포함되는데, n은 unsigned 자료형의 표현가능한 가장 큰 값이다. 이 규칙은 모호한 프로그래밍 에러를 발생시킬 수 있다.
int형 변수 i가 -10의 값을 가지고, unsigned int형 변수 u가 10의 값을 가진다고 가정해보자. 만약 우리가 iu를 < 연산자로 비교했다면, 우리는 1(true)이라는 결과를 예상할 것이다. 그러나 비교 전에, iunsigned int로 변환된다. 음수는 unsigned 정수로 표현될 수 없기 때문에, 변환된 값은 -10이 아닐 것이다. 대신에 값 4294967296이 더해질 것이고(4294967295는 unsigned int 값의 가장 큰 값이다) 변환된 값은 4294967286이다. i < u 비교는 그러므로 0의 결과를 만들게 된다. 어떤 컴파일러들은 signed 숫자와 unsigned 숫자를 비교했을 때 "comparison between signed and unsigned"와 같은 경고 메시지를 출력한다.
이것과 같은 함정 때문에, unsigned 정수는 가능하면, 특히 signed 정수들과 섞어서 사용하면 안된다.

Conversion During Assignment

usual arithmetic conversion은 대입에는 적용되지 않는다. 대신, C언어는 대입의 오른쪽 표현식이 왼쪽 변수의 자료형으로 변환되어야 한다는 간단한 규칙을 따른다. 만약 변수의 자료형이 표현식만큼 넓다면(wide) 장애없이 작동할 것이다.
부동소수점 숫자를 정수형 변수에 대입하는 것은 숫자의 분수 부분을 떼어내도록 한다.

int i;

i = 842.97;    // i is now 842
i = -842.97;   // i is now -842

만약 좁은(narrower) 자료형의 변수에 변수의 자료형의 범위를 벗어나는 값을 넣는다면 값을 대입하는 것은 의미없는 결과를 낸다.

c = 10000;    //char, WRONG
i = i.0e20;   //int, WRONG
f = 1.0e100;  //float, WRONG

"좁은(narrowing)" 대입은 lint와 같은 도구나 컴파일러에 의해 경고를 일으킨다.
만약 float 변수에 부동소수점 상수를 대입할 것이라면 f를 부동소수점 상수의 끝에 접미사로 붙이는 것은 좋은 생각이다.

f = 3.14159f;

접미사가 없다면 상수 3.14159double의 자료형을 가지게 되고, 경고 메시지를 발생시킬 가능성이 있다.

C99에서의 Implicit Conversions

C99에서의 암시적 변환(Implicit conversion)의 규칙은 C89에서와 다른 점이 있는데, 주로 C99는 _Bool, long long 자료형을 추가적으로 가지고 있기 때문이다.
형변환의 규칙을 정의하는 의도에 대해, C99는 각각의 정수형 자료형에 "integer conversion rank"라는 것을 두었다. 높은것부터 낮은것으로 아래에 나열했다.

1. long long int, unsigned long long int
2. long int, unsigned long int
3. int, unsigned int
4. short int, unsigned short int
5. char, signed char, unsigned char
6. _Bool

간단하게 보여주기 위해 확장된 정수 자료형과 열거형(enumerated) 자료형을 제외하였다.
C89의 integral promotions 대신에 C99는 integer promotion을 가진다. integer promotion은 intunsigned int보다 순위가 낮은 어떠한 자료형이든 int(제공되는 모든 자료형의 값이 int를 사용하여 표현이 된다면)나 unsigned int로 변환한다.
usual arithmetic conversion을 수행하는 C89와 C99의 규칙은 두가지의 경우로 나눌 수 있다.

  • 피연산자의 자료형이 모두 부동소수점 자료형일때 : 양쪽의 피연산자가 모두 complex 자료형처럼 길지 않다면, 규칙은 전과 동일하다.
  • 피연산자의 자료형이 둘다 부동소수점 자료형이 아닐 때 : 첫번째로 integer promotion이 각각의 피연산자에 행해진다. 만약 두 피연산자의 자료형이 이제 같아졌다면, 과정은 끝이 난다. 그렇지 않다면, 아래에 서술된 규칙에서 해당하는 하나가 적용된다.
    • 만약 양쪽의 피연산자가 둘다 signed 자료형이거나 unsigned 자료형을 가지고 있다면, 피연산자의 더 낮은 integer conversion rank를 가진 자료형이 더 높은 rank를 가진 자료형으로 변환된다.
    • 만약 unsigned 피연산자가 signed 피연산자보다 더 크거나 같은 rank를 가졌다면, signed 피연산자는 unsigned 피연산자의 자료형으로 변환된다.
    • 만약 signed 피연산자의 자료형이 unsigned 피연산자 자료형의 모든 값을 표현할 수 있다면, unsigned 피연산자는 signed 피연산자의 자료형으로 변환된다.
    • 그렇지 않다면 양쪽 피연산자를 signed 피연산자의 자료형에 대응되는 unsigned 자료형으로 변환한다.

우연히, 모든 arithmetic 자료형이 _Bool 자료형으로 변환될 수 있다. 만약 원래의 값이 0이면 변환의 값은 0이고 그렇지 않다면 결과는 1이다.

Casting

비록 C언어의 암시적 변환이 편리하더라도, 우리는 때때로 자료형 변환에 대해 더 많은 정도의 제어가 필요할 때가 있다. 이러한 이유로 C는 cast를 제공한다. cast의 형태는 아래와 같다.

( type-name ) expression

type-name은 표현식이 변환될 자료형을 명시한다.
아래의 예시는 float 값의 분수 부분을 계산하기 위해 cast 표현식을 사용하는 방법을 보여준다.

float f, frac_part;

frac_part = f - (int)f;

cast 표현식 (int)ff의 값을 int 자료형으로 변환하는 결과를 표현한다.
그 후 C언어의 usual arithmetic conversion은 뺄셈(subtraction)이 수행될 수 있기 전에 (int)f를 다시 float 자료형으로 변환한다. f(int)f의 차이는 f의 분수 부분이 cast 도중 없어졌다는 것이다.

cast는 컴파일러를 무효화하고 우리가 원하는 변환을 강제할 수 있도록 해준다.

float quotient;
int dividend, divisor;

quotient = dividend / divisor;

나눗셈의 결과는 정수가 될 것이고, 이 결과값이 float로 변환되어 quotient에 저장될 것이다. 하지만 우리가 나눗셈 전에 float로 변환한다면 더 사실적인 정답을 얻을 수 있다.

quotient = (float)dividend / divisor;

divisor는 cast를 필요로 하지 않는데, dividendfloat로 cast하는 것은 컴파일러가 divisorfloat로 변환하는 것 또한 강제하기 때문이다.
우연히, C언어는 ( type-name )을 단일(unary) 연산자로 여긴다. 단일 연산자는 이항 연산자보다 더 높은 우선순위를 가지고 있어서 컴파일러는 아래와 같이 해석한다.

(float)dividend / divisor

as

((float)dividend) / divisor

cast는 때로는 overflow를 피하기 위해 필수적이다. 아래의 예시를 보자.

/* in 16bit machine */
long i;
int j = 1000;

i = j * j;

대충 눈으로 보면 이 구문은 문제가 없어보인다. j * j의 값이 1,000,000이고, ilong자료형이니 이 크기의 값을 쉽게 저장할 수 있을 것이다.
그런데 문제는 두 개의 int가 서로 곱해질 때 발생하는데, 왜냐하면 결과값이 int 자료형이기 때문이다. j * j는 일부 machine에서는 int로 나타내기에는 너무 커서 overflow를 발생시킬 수 있다. 이 문제를 피하기 위해서 cast를 사용할 수 있다.

i = (long)j * j;

cast 연산자가 *보다 우선순위가 높기 때문에 첫번째 jlong자료형으로 변환되고, 두번째j도 강제로 동일하게 변환된다. 그런데 아래의 구문은 동작하지 않는다.

i = (long) (j * j);

당연히 overflow가 cast전에 이미 발생하기 때문이다.

5. Type Definitions

전에 Boolean 자료형을 #define이라는 매크로(macro)를 통해 정의한 적이 있었다.

#define BOOL int

Boolean 자료형을 정의하는 더 좋은 방법이 있는데, 이는 자료형 정의(type definition)으로 알려진 특징이다.

typedef int Bool;

자료형의 이름은 마지막에 자리한다. 첫 글자를 대문자로 작성하였는데, 이는 꼭 지킬 필요가 없다. 일부 C프로그래머들이 이용하는 관습일 뿐이다.
Bool을 정의하기 위해 typedef를 사용하는 것은 컴파일러가 스스로 인식할 수 있는 자료형에 Bool을 추가하도록 한다. Bool은 내장된 자료형과 동일하게 변수 선언, cast 표현식 등 어디에나 사용될 수 있다. 컴파일러는 Boolint와 똑같이 취급한다.

Type Definitions의 장점(Advantages)

Type definition은 프로그램을 더 이해하기 쉽게 만든다(프로그래머가 스스로 의미있는 자료형의 이름을 선택할 수 있기 때문). 예를 들어 cash_incash_out이라는 변수가 돈의 총량을 저장하는 것에 사용된다고 생각해보자.

typedef float Dollars;

Dollars cash_in, cash_out;

float cash_in, cash_out;

float보다는 Dollars가 더 전달력이 있다. type definition은 프로그램을 더 수정하기 쉽게 만든다. 만약 Dollarsdouble로 정의해야 하도록 한다면 우리는 type definition만 수정하면 된다.

typedef double Dollars;

Dollars 변수의 선언은 변하지 않는다. type definition이 없다면 우리는 모든 float 변수의 자리를 찾아야 하고 그들의 정의를 바꿔야한다.

Type Definitions and Portability

Type definition은 이식성좋은(portable) 프로그램을 작성하는 중요한 도구이다. 프로그램을 한 컴퓨터에서 다른 컴퓨터로 옮기는 많은 문제 중 하나는 컴퓨터마다 다른 자료형을 가지고 있다는 점이다. 만약 32bit 컴퓨터에서 100000의 값을 가진 변수는 16비트 컴퓨터에서 overflow의 문제를 야기할 수 있다.

typedef int Quantity;

Quantity q;

int의 범위가 더 작은 컴퓨터로 프로그램을 이동시킬 것이라면 아래와 같이 수정하기만 하면 된다.

typedef long Quantity;

Quantity q;

이 기술은 모든 프로그램에 작동하는 것은 아니다. Quantity의 정의를 바꾸는 것은 Quantity 변수가 사용되는 방법에 영향을 미치기 때문이다. 최소한, printfscanf의 호출에 Quantity 변수가 사용되었다면 %d 변환 명시자가 %ld로 대체되어야 하기 때문이다.

C의 라이브러리는 자체적으로 C의 구현에 따라 아주 다양하게 나타날 수 있는 자료형의 이름을 만드는 것에 typedef를 사용한다. 이러한 자료형은 보통 _t로 끝나는 이름을 가지는데, 예시로 ptrdiff_t, size_t, wchar_t와 같은 것이 있다. 이것들의 실제 정의는 자료형마다 다르지만, 아래의 전형적인 예시가 있다.

typedef long int ptrdiff_t;
typedef unsigned long int size_t;
typedef int wchar_t;

C99에서는 <stdint.h> 헤더는 typedef를 사용하여 특정 bit의 숫자를 정수로 정의했다. 예를 들어 int32_t는 32비트에서의 정수 자료형이다. 이러한 자료형을 사용하는 것은 프로그램을 더욱 이식성좋게(portable) 만든다.

6. The sizeof Operator

sizeof 연산자는 특정 자료형이 값을 저장하는 것에 얼마나 많은 메모리를 필요로 하는지 프로그램이 알게 해준다.

sizeof ( type-name )

위 표현식의 값은 type-name에 속해있는 것이 값을 저장하는 데에 몇 byte나 필요한지를 표현하는 unsigned 정수이다. sizeof(char)는 항상 1이지만, 다른 자료형들은 다양할 수 있다. 32bit machine에서는 sizeof(int)는 보통 4이다. 컴파일러 자체가 일반적으로 sizeof 표현식의 값을 결정할 수 있기 때문에 sizeof 연산자는 특이한 연산자이다.

sizeof 연산자는 상수, 변수, 표현식에 일반적으로 적용될 수 있다. ijint형 변수라면, sizeof(i)는 32bit machine에서 4이다. sizeof(i + j)도 동일하다. 자료형이 아닌 표현식에 적용될 때에는 sizeof가 괄호를 필요로 하지 않는다. sizeof(i) 대신 sizeof i로 써도 된다. 하지만 연산자 우선순위 때문에 어떤 방식으로든지 괄호가 필요할 수 있다. sizeof i + j를 컴파일러가 해석할 때에는 (sizeof i) + j로 해석되는데, 이유는 sizeof가 단일(unary) 연산자라 이항(binary) + 연산자보다 높은 우선순위를 가지기 때문이다. 이러한 문제를 피하기 위해 sizeof 연산자에 괄호를 항상 넣는 것이 좋다.
sizeof 값을 출력하는 것에는 주의가 필요한데, sizeof 표현식의 자료형이 size_t라는 이름의 implementation-defined 자료형이기 때문이다.

C89에서는 출력하기 전에 표현식의 값을 알려진 자료형으로 변환시키는 것이 바람직하다. size_t는 unsigned 정수 자료형임이 보장되어 있기 때문에 sizeof 표현식을 unsigned long으로 변환시키는 것이 가장 안전하다(C89의 가장 큰 unsigned 자료형). 그리고 이를 출력할 때에는 %lu 변환 명시자를 사용한다.

printf("Size of int: %lu\n", (unsigned long) sizeof(int));

C99에서는 size_t의 자료형이 unsigned long보다 클 수가 있다. 그러나 C99의 printf 함수는 size_t 값을 cast 없이 직접적으로 표시하는게 가능하다. 이 트릭은 단어 z를 변환 명시자에 사용하는 것으로 가능한데, 아래의 일반적인 코드를 보자.

printf("Size of int: %zu\n", sizeof(int));  //C99 only


Others


%o%x는 unsigned 정수로 8진, 16진 표현을 할 수 있다. 그러면 일반적인 signed 정수는 8진수나 16진수로 어떻게 써야 하나?

값이 음수가 아닌 한 signed 정수를 %o%x로 출력하는 것에 사용할 수 있다.
이러한 변환은 printf가 signed 정수를 unsigned인것처럼 취급하게 하는데, 이걸 다른 말로 표현해보면 printf는 sign bit를 숫자의 정도를 나타내는 bit로 본다는 것이다. sign bit가 0이면 아무 문제가 생기지 않지만, sign bit가 1이라면 printf는 예상치 못하게 큰 숫자를 출력할 것이다.

그러면 숫자가 음수이면 어떻게 해야하나? 이걸 8진이나 16진법으로 표현할 수 있나?

음수를 8진과 16진으로 표현하는 직접적인 방법은 없다. 이렇게 할 필요가 정말 거의 없기 때문이다. 당연하게도, 숫자가 음수라면 이 숫자를 테스트하여 minus 표기를 직접 출력해주어야 한다.

if (i < 0)
    printf("-%x", -i);
else
    printf("%x", i);

왜 부동소수점 상수는 double의 형태로 저장되고, float의 형태로 저장되지 않는가?

이는 역사적인 이유가 있는데, C언어는 double 자료형을 선호한다. float는 이등 시민(second-class citizenㅋㅋㅋㅋ)으로 간주된다. k&R에서 이에 토론한다고 생각해보자.
"float를 사용하는 가장 큰 이유는 넓은 배열에서 공간을 절약하고, 많이 일어나진 않지만 double-precision arithmetic이 특별하게 쓰기 힘들어서 시간을 절약해야 하는 machine이 있다면 사용할 것이다." C언어는 근원적으로 모든 부동소수점 산술이 double precision으로 이루어지는 것을 필수로 한다.(물론 C89와 C99에서 이를 꼭 필요로 하지는 않는다)


16진 부동 소수점처럼 보이는 것들은 무엇을 하고, 이것들이 왜 좋은가?

16진 부동소수점 상수는 0x 또는 0X로 시작하고, 반드시 단어 P(또는 p)가 선행되는 exponent를 포함해야한다. exponent는 sign을 가져야 하고, 그 상수는 항상 f, F, l, L로 끝나야 한다. exponent는 10진으로 표현되어야 하지만, 10의 거듭제곱이 아닌 2의 거듭제곱으로 표현한다. 예를 들어 0x1.Bp3은 숫자 1.6875 * (2^3) = 13.5를 표현한다. 16진수 자릿수인B는 bit 패턴 1011에 대응한다. 마침표 오른쪽에서 B가 나타나는데, 각각의 1bit는 2의 음수의 거듭제곱으로 나타난다. 이 거듭제곱들을 다 더하면 (2^(-1) + 2^(-3) + 2^(-4))이고, .6875의 결과가 나온다.
16진 부동소수점 상수는 매우 정확한 상수(수학적 상수 eπ와 같은)를 표현하는 것에 사용한다. 16진 숫자는 명확한 2진 표현을 하는데에 반해, 10진으로 쓰인 상수는 2진으로 변환할 때 작은 반올림의 오류의 영향을 받는다.
16진 숫자는 <float.h> 헤더 내부의 매크로의 값과 같은 아주 큰 값을 가진 상수를 정의하는 것에도 유용하다. 이 상수들은 16진으로는 쉽게 쓸 수 있지만 10진으로 쓰기는 어렵다.


double 값에 %f가 아니고 %lf를 사용하여 출력해야 하는가?

이는 대답하기 어려운 질문이다. 첫번째로, scanfprintf는 매개변수의 숫자가 제한되어 있지 않은 평범하지 않은 함수이다. 우리는 scanfprintf가 가변길이(variable-length) 매개변수 리스트를 가졌다고 말한다. 가변길이 매개변수 리스트가 있는 함수가 호출될 때, 컴파일러는 float 매개변수를 자동적으로 double 자료형으로 변환시킨다. 결과적으로 printffloatdouble 매개변수를 구문하지 못한다. 이는 왜 %fprintf 함수에서 floatdouble에 둘 다 작동하는지 알 수 있게 설명해준다.
그렇지만 scanf에서는 변수를 pointer로써 넘긴다. %fscanf가 넘겨진 주소에 float 값을 저장하도록 하고, %lf는 넘겨진 주소에 double 값을 저장하도록 한다. 여기서 floatdouble의 구별은 아주 중요하다. 만약 잘못된 변환 명시자(conversion specification)가 주어졌다면,scanf는 잘못된 byte의 숫자를 저장할 수 있다(float의 bit pattern이 double과 같지 않다).


문자(character) 변수가 signed인지 unsigned인지 문제가 될 때는 언제인가?

만약 변수에 7 bit 문자만 저장한다면 문제가 되지 않는데, sign bit가 0일 것이기 때문이다. 하지만 만약 8bit 문자를 저장하기로 계획했다면, 우리는 unsigned char 자료형에 저장하도록 할 것이다. 아래의 예시를 보자.

ch = '\xdb';

chchar로 정의되어 있다면, 컴파일러는 이를 signed 문자로 취급할 것이다(많은 컴파일러가 이렇다).ch가 오직 문자로만 쓰인다면 이는 별 문제가 되지 않을 것이다. 그러나 ch가 컴파일러가 이 값을 정수로 바꾸는 것을 필요로하는 맥락에서 사용된다면, 문제가 생긴다. 정수의 결과값이 음수값일 것인데, ch의 sign bit가 1이기 때문이다.
또다른 상황을 생각해보자. 어떤 종류의 프로그램에서, char 변수를 1byte의 정수를 저장하는 것에 사용한다고 해보자. 만약 우리가 이러한 프로그램을 작성한다면, 우리는 각각의 변수가 signed char인지 unsigned char인지 결정해야 한다. 일반적인 정수형 변수가 int 인지 unsigned int인지 결정하는 것과 같게 말이다.


\? escape sequence의 의도는 무엇인가?
\? escape는 ??로 시작하는 trigraph sequence와 관련이 있다. 문자열 안에 ??를 넣어야 한다면, 컴파일러가 trigraph의 시작인 것으로 착각할 가능성이 있다. 두번째 ?\?로 대체하는 것으로 이 문제를 해결할 수 있다.


getchar가 빠르다면, 각각의 문자를 읽을 때 지금까지 왜 scanf를 사용했을까?

getchar보다 scanf가 느리다고 하더라도 scanf함수가 더 유연하다(flexible). 우리가 이전에 봤던 것 처럼 "%c" 서식 문자열은 scanf 함수가 다음 input 문자를 읽도록 하는데, " %c"는 공백 문자가 아닌 다음 문자를 읽도록 한다. 또한 scanf는 다른 종류의 데이터와 섞여있는 문자를 읽는 것에 유용하다. input data가 정수, 숫자가 아닌 단일 문자, 그리고 또다른 정수로 이루어져 있다고 생각해보자. 서식문자열 "%d%c%d"를 사용하는 것으로, 우리는 scanf가 3개의 항목을 모두 읽을 수 있도록 할 수 있다.


integral promotion은 어떤 상황에서 문자나 short 정수를 unsigned int로 바꾸는 것일까?

integral promotions은 만약 int 자료형이 원래 자료형의 모든 가능한 값을 포함할 정도로 크지 않다면 unsigned int를 산출한다. 문자가 일반적으로 8bit만큼 길기 때문에, 대부분 거의 항상 최소 16비트의 길이를 보장하는 int로 변환된다. signed short 정수도 항상 int로 변환될 수 있다. unsigned short 정수는 문제가 있다. 만약 short 정수가 일반적인 정수와 같은 길이(16bit machine처럼)를 가졌다면, unsigned short 정수는 unsigned int로 변환된다. 왜냐하면 가장 큰 unsigned short 정수(16bit machine에선 65535)가 가장 큰 int(32767)보다 크기 때문이다.


만약 변수가 값을 저장하기에 충분히 넓지 않으면 실질적으로 무슨일이 일어날까?

대략적으로 말하면, 만약 값이 integral 자료형이고, 변수가 unsigned 자료형이라면, 추가적인 bit는 없어진다. 만약 변수가 signed 자료형이라면 결과는 implementation-defined이다. 부동소수점 숫자를 저장하기에 넓이가 충분하지 않은 변수(정수 또는 부동소수점)에 부동소수점 숫자를 대입하면 이는 Undefined behavior이다. 프로그램 종료와 같은 어떠한 일도 일어날 수 있다.


왜 C언어는 type definition을 제공하는 것에 귀찮아하는 것인가? typedef를 사용하여 Bool타입처럼 좋은 BOOL 매크로를 정의할 수는 없나?

type definition과 macro definition에는 두 가지 중요한 차이점이 있다.
첫번째, type definition은 macro definition보다 더욱 powerful하다. 특히, 배열과 포인터 자료형은 macro로써 정의되지 않는다. 우리가 포인터를 정수 자료형으로 정의한 macro를 사용한다고 가정해보자.

#define PTR_TO_INT int *

PTR_TO_INT p, q, r;

위의 선언은 전처리후 아래와 같이 된다.

int * p, q, r;

불행하게도 p만 포인터이고 qr은 일반적인 정수형 변수가 된다. type definition은 이러한 문제를 가지지 않는다.

두번째, typedef 이름은 변수로써 같은 scope rule의 영향을 받는다. 함수 내부에서 정의된 typedef는 바깥 함수에서는 인식되지 않는다. 그러나 macro 이름은 어디에 있던지 상관없이 전처리기에 의해 대체된다.


컴파일러가 보통 sizeof 표현식의 값을 결정할 수 있다고 했는데, 컴파일러가 항상 sizeof 표현식의 값을 결정하지는 못하는가?

C89에서는 결정할 수 있다. 하지만 C99에서는 한 가지 예외가 있다. 컴파일러는 가변 길이 배열의 크기를 결정할 수 없는데, 왜냐하면 배열 내부의 요소(element)의 숫자가 프로그램 실행중에 변할 수 있기 때문이다.

profile
저장용

0개의 댓글