가변인자함수에 진급이 적용되는 이유는 무엇인지요.

tomas의 이미지

printf함수같은 가변인자함수의 인자는
default argument promotion이 발생하는것으로 알고있습니다.
그런데 이런것이 왜 필요한지 모르겠습니다.
어차피 첫번째 인자에서 "%d %f"이런식으로 인자의
데이터형을 알려줄수있으니
굳이 int나 unsigned int로 진급을 시켜야하나요?

단지, 가변인자함수에서 여러데이터형의 인자를 받으면
함수를 구현하기가 조금 힘들어져서 그런건지요?

lsj0713의 이미지

아마도 표준 이전의 전통 혹은 현실적인 이유 때문일 것입니다.

보통 int형은 해당 환경에서 가장 빨리 접근할 수 있는 크기로 선택됩니다. 따라서 char형 보다는 int형을 되도록 많이 사용하는 것이 유리합니다.

그 때문인지는 몰라도, (C++이 아닌) C에서는 수식의 계산에서 int형과 double형을 선호하는 경향이 있습니다. (접미사를 붙이지 않은 숫자 상수의 경우 int형과 double형이 우선됩니다. 문자 상수'A'의 타입은 int형입니다.) 함수의 매개변수로 수식이 사용되었을 경우, 그 결과값이 char형 혹은 float형이 될 확률은 적습니다. 보통의 함수라면 모르지만, 가변 인자 함수라면 굳이 char형이나 float형을 배려할 필요성은 적습니다(필요하다면, 포인터를 이용한다던가 하는 여러가지 돌아가는 길 또한 존재합니다).

구현하기 힘들 것까지는 없겠지만, 가변인자의 매개변수로 가능한 데이터형이 한정되면, 신경써야 될 경우의 수가 줄어드는게 사실입니다. 게다가 어차피 대부분의 CPU에선 정렬제한 때문에 char형을 다루는 것이 꺼려질 테니까요.

tomas wrote:

어차피 첫번째 인자에서 "%d %f"이런식으로 인자의
데이터형을 알려줄수있으니
굳이 int나 unsigned int로 진급을 시켜야하나요?

printf 함수는 특별한 케이스일 뿐입니다. 가변인자 함수가 꼭 printf 함수처럼 format string을 가져야 되는 것은 아닙니다. 컴파일러는 가변인자 함수에서 몇개의 인자가 넘어왔는지, 혹은 각각의 타입이 무엇인지 전혀 알지 못합니다. 그걸 처리하는 것은 전적으로 프로그래머의 역할입니다.

ps. 가변인자 함수의 매개변수로 넘어갈 수 있는 데이터형의 종류를 세어보면 고려해야 될 경우가 몇개 안된다는걸 쉽게 알 수 있습니다.

int, long, double, long double, 포인터형, 구조체 or 공용체

모두 다 무조건 4바이트 단위로 처리할 수 있습니다. 컴파일러를 만드는 입장에서 본다면 아주 매력적이라 할 수 있겠지요.

cinsk의 이미지

ANSI C 표준이 만들어지기 전에는 function prototype이라는 것이 존재하지 않았습니다. 따라서 보통 다음과 같이 함수를 만들어 썼습니다:

double foo();

main()
{
  char ch;
  float f;
  ...
  foo(ch, f);
  ...
  exit(0);
}

double foo(i, d)
  int c;
  double d;
{
  ...
}

보통 이런 코드를 기계어로 바꾸면, 함수 호출은 함수에 전달되는 인자를 stack에 push하고, 함수 시작 주소로 jump합니다. 그런 다음, 그 함수 내부에서 stack에 쌓인 인자들을 꺼내서 쓰는 것이죠.

그런데, function prototype이 없으면, 이 함수의 정의를 보지 않고서는 어떤 타입의 인자가 몇 개가 전달될 지 전혀 알 방법이 없습니다. 위에서는 double foo()라고 함수 선언을 제공했기 때문에 리턴 타입이 double이라는 것은 알았지만, 이것이 foo 함수가 인자를 전혀 안받는다고 말하는 것이 아닙니다. 즉 이 경우에 이 함수의 선언은 ANSI C에서 double foo(void)와는 다릅니다.

어떤 시스템에서 다음과 같이 data type의 크기가 정해져 있다고 가정해 봅시다.

char - 1 byte
short - 2 byte
int - 4 byte
long - 4 byte
float - 4 byte
double - 8 byte

그리고 나서 main()에서 foo()를 호출하는 부분을 봅시다. 아직 컴파일러는 foo()가 어떤 타입의 인자를, 몇 개 받는지 알지 못하는 상황입니다. 이 때, main()에서는 foo()를 부를 때, 첫번째 인자로 char type을, 두 번째 인자로 float type을 전달했습니다. 이 경우 (가상의) 기계어로, foo() 호출을 다음과 같이 표현할 수 있습니다:

push (byte) [c]
push (4byte) [f]
jump foo

즉, stack에는 5 byte 분량의 데이터가 들어가게 되고, foo() 함수로 점프하게 됩니다. 그런데, 컴파일러가 나중에 foo()의 정의를 보면, 실제 foo() 함수는 첫번째 인자로 int를, 두번째 인자로 double을 받는 것을 알게됩니다. 그렇다면 foo() 함수의 초기 부분은 기계어로 다음과 같이 만들어 질 것입니다:

pop (4byte) i
pop (8byte) d
...

즉 이 함수는 stack에서 12 byte 분량의 데이터를 꺼내어 작업하게 됩니다. 이 것은 앞에서 foo()를 부를때와 완전히 다른 코드가 되며, 결국 이 프로그램은 정상적으로 동작할 수 없습니다. (대개의 경우 segmentation fault가 발생)

"Default argument promotion"은 이 문제를 해결해 줍니다. int보다 작은 정수 타입은 모두 int로, float은 double로 바뀌는 것을 의미하며, 이 규칙에 따르면 위 main()에서 foo()를 부를 때 다음과 같이 바뀝니다:

expand (1 byte) c -> (4 byte) c1
push c1
expand (4 byte) f -> (8 byte) d
push d
jump foo

결국, "default argument promotion"을 쓰면, 함수의 실제 정의를 보지 않더라도, 함수 호출이 제대로 이루어지도록 할 수 있습니다.

후에, ANSI C가 나온 다음에는, function prototype (즉 선언이지만, 함수에 전달되는 인자의 갯수와 타입을 지정하는 선언)이 소개되었기 때문에, 정의를 보지 않더라도, 함수의 prototype을 보고, 함수 호출에 정확한 기계어를 만들어 낼 수 있습니다.

그런데!, function prototype이 나왔다 하더라도, 가변 인자를 받는 함수에서, 가변 인자 부분에 전달되는 인자의 타입을 알 수 없는 것은 마찬가지 입니다. 따라서 ANSI C (이후 현재 C99 표준까지) 이후에도, 가변 인자 부분에는 똑같이 "default argument promotion"이 일어납니다.

그럼.

익명 사용자의 이미지

감사합니다. ^^

그런데 또 궁금한점이 생기네요.. :oops:

가변인자함수에서 default argument promotion을 하더라도
double 형도 있고 long double형도 있는데요.
gcc에서 double형은 8바이트 long double형은 12바이트인데...
진급을 해서 char같은건 4바이트로 고정시킨다고 해도
기본인자 진급결과의 바이트수가 8도있고 12도 있으므로
인자를 받는 함수쪽에서 4바이트를 읽어야 할지 12를 읽어야할지에 대한 정보를 알수없지 않을런지요..

익명 사용자의 이미지

다시한번 읽어보니 조금 이해가 가네요~
그러니까... 큰데이터형으로 무조건(?) 바꿔서 만약을 대비한다.
정도로 해석이 되는데요..
그렇다고 해도 long double형은 double형보다 크기가 큰데 여기서 문제가 생기지 않을까요?
가장 큰 long double형으로 진급이 일어나야지 왜 double형으로 진급이 일어날까요... :oops: :cry: :oops:

doldori의 이미지

Anonymous wrote:
가변인자함수에서 default argument promotion을 하더라도
double 형도 있고 long double형도 있는데요.
gcc에서 double형은 8바이트 long double형은 12바이트인데...
진급을 해서 char같은건 4바이트로 고정시킨다고 해도
기본인자 진급결과의 바이트수가 8도있고 12도 있으므로
인자를 받는 함수쪽에서 4바이트를 읽어야 할지 12를 읽어야할지에 대한 정보를 알수없지 않을런지요..

네, 알 수 없습니다. 그래서 그에 대한 정보를 어떤 방식으로든 알려줘야 하는데
printf에서는 %d, %f 같은 서식문자열을 통해 알려줍니다. 그러나 이것은 함수의
프로토타입에 의해 알려주는 방식과는 근본적으로 차이가 있고, 일반적인 컴파일러는
가변 인자의 형에 대한 검사를 하지 않으므로 가변 인자 함수는 안전하지 않다고
하는 것입니다.

Anonymous wrote:
그러니까... 큰데이터형으로 무조건(?) 바꿔서 만약을 대비한다.
정도로 해석이 되는데요..
그렇다고 해도 long double형은 double형보다 크기가 큰데 여기서 문제가 생기지 않을까요?
가장 큰 long double형으로 진급이 일어나야지 왜 double형으로 진급이 일어날까요...

무조건 큰 타입으로 변환한다고 해서 안전한 것이 아닙니다. 중요한 것은
크기가 아니라 "정확한 형"입니다. 두 분의 답변을 자세히 읽어보면 아시겠지만
char, short(무부호형 포함)와 float만을 기본 인자 진급의 대상으로 정한 것은
표준 이전 방식의 잔재입니다. 그리고 인자의 형을 결정할 수 없는 가변 인자
함수를 기본 인자 진급을 통해 처리하는 것은 표준이 정한 일종의 "선택"입니다.
물론 이런 선택 사항을 다른 방식(예를 들면 정수형은 long long으로, 부동형은
long double로 진급하는 방식)으로 바꾸는 것도 가능하기는 하지만(바람직하다는
뜻은 아님), 그렇다고 해서 안전해지는 것도 아니고 오히려 효율이나 이전 코드와의
호환성을 해치는 결과만을 낳게 될 것입니다.
tky7068의 이미지

참고되었습니다. 감사합니다. ^^

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.