[C] 함수로 전달된 포인터

포인터를 함수의 인자로 받는 경우, 함수내에서 원본을 변경하는가.

내용이 너무 두서 없어서 포스트 전체를 수정합니다.

처음 의문이 든 부분은, 문자열을 가리키는 포인터를 함수로 넘겨주고 문자열을 변형하면 포인터의 값이 변하는가?라는것이었는데. 이는 사실 그리 어려운 문제가 아니다. 포인터는 메모리의 주소를 가리키는 타입의 변수이고 이는 사실 unsigned int 나 unsinged int64 등의 정수형과 비슷하게 숫자를(메모리 번지도 결국 숫자값이므로) 담는 변수이다.

그리고 함수에 선언된 인자는 함수 내부에서만 사용하는 지역변수이고, 표준 타입의 변수를 함수에 전달하면 그 값이 인자로 선언한 변수에 들어가는데, 이건 그냥 변수의 값이 인자에도 들어가는 것이므로 값이 복사되는 것이다. 따라서,

  • 함수 외부에서 특정한 구조체나 배열, 문자열에 대한 포인터를 저장한 변수 A가 있고 이를 함수의 인자로 넘기면
  • 함수의 인자 B는 A와 동일한 값을 갖는다. 다만 값이 같을 뿐, B가 A를 가리키는 포인터가 될 수는 없다.
  • A가 포인터라면 B도 포인터이므로, 이 포인터가 가리키는 곳의 값을 조작할 수는 있다. 이러한 조작은 함수의 종료 후에도 변경을 유지하게 된다.
  • B는 A의 사본으로서, 함수의 실행 중에만 유지되고 함수 실행 종료시점에 파괴된다. A에 들어있는 값 자체는 아무런 영향을 받지 않는다.

로 정리할 수 있겠다. 땅땅땅.

C에서의 문자열 배열과 문자열 배열을 동적으로 할당하기

문자열의 배열을 2차원배열을 쓸 때의 문제점

C언어는 문자열을 다루는 자료형이 없다. 대신에  C에서 문자열은 널 문자로 끝나는 문자(char) 타입의 배열을 사용하여 저장한다.  만약 일련의 문자열 집합을 다루려면 문자열의 배열을 써야 할테다.  여러 개의 문자열을 배열에 담고 싶다면 이차원 배열을 쓰는 방법을 생각할 수 있는데,  이 경우 크기가 일정하게 고정된 영역을 여러 개 생성해야 하고, 만약 각 문자열의 길이가 제각각 다르다면 불필요하게 낭비되는 메모리가 제법 될 수 있다.

2차원 배열의 문자열 배열
2차원 배열을 사용하여 구성한 문자열 배열

메모리를 비롯하여, 부족한 하드웨어 자원을 알뜰 살뜰 아껴서 최대의 성능을 내기 위해 개발된 초기 C언어 관점에서 이러한 2차원 배열의 사용은 그리 추천하지 않는다. C에서의 문자열 배열과 문자열 배열을 동적으로 할당하기 더보기

[C/C++] 가변인수를 받는 함수

C함수의 가변인수

C함수도 가변인수를 사용할 수 있다. 대표적인 예가 printf이다. printf에는 서식 문자열을 비롯하여 서식에 채워질 값들을 컴마로 연결하여 나열할 수 있다. 이러한 가변인수는 어떻게 만들어서 사용할 수 있는지 살펴보자.

가변인수 함수의 선언

가변 인수 함수는 가변적으로 받을 인수 대신 을 사용하는 것으로 가변 인수 함수임을 컴파일러에게 알릴 수 있다. 인수에 …이 들어있는 경우 컴파일러는 인수의 개수나 타입에 대해서는 전혀 신경을 쓰지 않게 된다. (결국 이는 함수 구현 시 프로그래머가 일일이 체크해야 하는 부분이다.) 이 때 중요한 것은 고정적으로 사용하는 인수는 반드시 하나 이상 있어야 한다. 즉 int myFunc(…) 과 같이 쓰는 것은 안된다. 최소한 하나의 인수는 고정적으로 정의되어 있어야 한다. 올바른 가변 인수 함수의 선언은 int myFunc(int argc, …) 과 같은 모양이 되어야 한다.

가변 인수의 사용

컴파일러는 가변 인수에 몇 개의 인수가 들어오는지 전혀 신경쓰지 않는다. 따라서 가변이라고 하더라도 몇 개의 인수를 받을지는 알 수 있는 방법이 있어야 한다. 대부분의 가변 인수는 고정인수로 가변 인수의 개수를 받는 편을 택한다.

가변 인수의 획득

가변인수는 va_list 타입의 지역변수 ap를 선언하고 이를 통해 개별 인수를 획득할 수 있다. 다음과 같은 방식을 사용한다.

/*가변인수 함수 내*/

va_list ap;
va_start(ap,마지막_고정인수);
루프시작{
    arg = va_arg(ap, 변수의 타입);
    /*모든 가변 인수를 획득할 때 까지 이 루프를 반복한다*/
}
va_end(ap);

마지막 고정인수는 가변 인수를 획득하기 위해 꼭 필요하다. 왜냐하면 가변인수는 포인터 배열로 전달되기 때문에 시작번지를 알고 있어야 한다. 그 가변 인수의 시작번지는 마지막 고정인수의 번지의 바로 옆 위치가 되기 때문이다. (*함수의 인수로 전달된 변수들은 해당 함수의 지역 변수와 동등하게 취급된다.)

이렇게 루프를 돌며 각 변수의 타입을 지정하여 해당 인수를 획득할 수 있다. 특이한 점은, va_arg()라는 함수가 변수의 타입을 인수로 받고 있는데, 정상적인 C문법에서는 변수 타입을 인수로 받을 수 없다. 아마 C함수가 아니라 매크로로 짜여진 함수가 아닐까 생각된다. 즉 ap는 문자열형 포인터 변수이고, 루프를 돌면서 해당 포인터에서 지정된 타입형의 크기만큼의 값을 읽어서 그 내용을 꺼내주는 역할을 할 것이다.

이렇게 모든 가변 인수를 획득한 다음, va_end(ap); 구문을 써서 마무리 작업을 해줘야 한다. (사실 할 필요는 없는데 혹시 모르니.)

이 때, 계속해서 강조하는 것은 컴파일러는 어디서부터 어디까지가 인수로 넘겨진 값인지 알 수 없으니, 적절한 개수만큼의 인수를 얻어와야 한다. 가장 쉬운 방법은 고정 인수 중 하나에 가변 인수의 개수를 알려주는 방법이 있고, 혹은 문자열 배열을 처리하는 것과 마찬가지로 0 혹은 -1과 같이 특이한 값을 마지막 가변인수로 넣어주도록 하는 방법이 있을 수 있다.

다음은 가변 인수를 통해 함수에 집어 넣는 숫자들을 모두 더한 합을 리턴해주는 예제이다. 고정 인수로 가변 인수의 개수를 넘겨주는 방식을 택했다. va_list를 사용하기 위해서는 stdarg.h 를 반입해야 한다.

#include <stdio.h>
#include <stdarg.h>

int getSum(int num, ...)
{
	int sum = 0;
	int i;
	va_list ap;
	int arg;

	va_start(ap, num);
	for(i=0;i<num;i++){
		arg = va_arg(ap,int);
		sum += arg;
	}

	va_end(ap);
	return sum;

}

int main(void)
{
	printf("1+2=%d\n",getSum(2,1,2));
	printf("1+2+3+4+5=%d\n",getSum(5,1,2,3,4,5));

	return 0;
}

 그럼 printf는 어떻게 하고 있나

처음에 언급했던 printf는 그럼 어떻게 하고있나? 이 함수를 쓸 때는 가변인수의 개수를 명시하지도 않고 있고, 심지어 가변 인수의 타입도 뒤죽 박죽이다. 하지만 고정인수로 보내는 “서식 문자열”에 포함된 %d, %s, %c 등의 포맷팅 문자들의 개수를 세고, 또 각각의 문자가 받아야 할 인수의 타입을 설명해주므로 적절히 처리할 수 있을 것 같다. 아무튼 가변 인수 함수는 이런 식으로 만들어 진다.