[고급 C++] 가변인자 / CERT C (feat. 안전 코딩하기)

 

 

 

   가변 인자

 

가변인자

특성

- 함수 인자의 수가 정해져있지 않은 함수

- C는 객체지향과 달리 함수명으로만 함수를 구분

- 인자리스트만 다를 경우, 가변인자 함수를 작성하면 하나의 함수로 가능

- 가변인자를 위한 매크로有

- 고정할당 뒤에 차례대로 저장됨

 

형태

int 함수명(인자, …)

…; 인자 생략 가능, 여러개 전달 가능

 

 

 

가변인자를 위한 매크로

특성

- ANSI C에서는 이식성을 높일 목적으로 가변길이 인수를 사용하기 위한 매크로를 제공

- 헤더파일 : #include <stdarg.h>

- va_start / va_arg / va_end

 

va_start(list,fix)

- 리스트를 초기화

- 리스트 포인터 고정 다음인자의 시작주소를 리스트에 저장함

- va_list : 헤더파일에 어떤 주소도 대입받을 수 있는 void형 포인터

 

va_arg(list, type)

- 리스트에서 타입만큼 읽어들임

- 가변인자 리스트를 하나씩 접근하기 위해 사용

 

va_end(list)

- 리스트를 NULL 포인터로 저장

- 가변인자의 사용이 끝날 때 사용

 

 

예시

     1  #include <stdio.h>

     2  #include <stdarg.h> // 가변 파라미터 헤더

     3  int main()

     4  {

     5     int result;

     6     result = add(3, 10, 20, 30);

     7     printf("result : %d \n", result);

     8

     9     result = add(5, 10, 20, 30, 40, 50);

    10     printf("result : %d \n", result);

    11

    12

    13     return 0;

    14  }

    15

    16  int add(int count, ...) // 가변인자를 갖는 함수

    17  {

    18     int i, sum = 0;

    19     va_list list; // var_list 선언 void형포인터

    20

    21     va_start(list, count);

    22

    23     for(i=0;i<count;i++)

    24        sum += va_arg(list, int);

    25

    26     va_end(list);

    27

    28     return sum;

    29  }

result : 60
result : 150

 

 

 

 

  CERT C

소프트웨어 시스템의 안전성, 신뢰성 및 보안 향상을 위한  C 프로그래밍 언어에 대한 소프트웨어 코딩 표준

안전한 전처리 작업

헤더파일에서 다른 파일을 #include 하는 것

- -I 로 비표준 헤더파일이 들어있는 디렉토리를 지정해줄 것

- define 문은 “;”로 끝나면 안됨

- define문은 헤더파일에 담을 것

- 소스코드는 각자 컴파일이 일어나므로 각 헤더파일에 선언해주어야함 

- 관리의 편리성을 위해 공통적으로 사용하는 부분은 헤더에 넣음

 

버그 없는 C포인터 사용

1. 배열의 크기를 얻을 때 포인터로 sizeof() 사용하지말 것

 - sizeof() 연산자는 피 연산자의 크기를 반환하며 sizeof가 포인터로 배열의 크기를 결정하기 위해 사용될 때 원치 않는 결과를 가져올 수 있음

- void funcA(int arr[5])는 4Byte * 5를 할당받은 것이 아니라 주소 하나를 받은 것으로 4Byte만큼만 할당됨

* void funcA(int arr[5])= void funcA(int arr[]) = void funcA(int *arr)

 

 

2. 표현식에서 배열타입이 호환 가능함을 보장할 것

- 표현식에서 호환되지 않은 두 개 이상의 배열을 사용하면 정의되지 않은 동작을 초래함

- 배열 주소를 받을 수는 있으나 의도하지 않은 값이 저장됨

- 두 배열의 타입은 호환 가능해야 하며, 같은 원소타입을 갖고있어야 함

 

 

3. 반복 제어문에서 연산식이나 함수를 호출하지말 것

반복할 때마다 연산을 수행하거나 함수를 호출하면 성능이 저하됨

 

4. 문자열 상수를 가리키는 포인터는 const로 선언할 것

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	// "" : 문자열 상수
	char c[] = "hello";  //  \0까지 6byte 할당

	// 문자열이 수정될 경우 할당 대신 초기화를 사용한다.
	c[0] = 'H';  // 항상 변경 가능
	printf("c : %s \n", c);

	// 문자열 상수를 가리키는 포인터는 const로 선언
	// char *p = "hello";       // char c[]와 다름. char *p는 주소를 보관
								// 변경하려면 런타임 오류

	// p[0] = 'H';  // 컴파일러마다 다름(리눅스 오류, VS 버전마다 다름),
					// 따라서 const로 상수화 해라

	const char *p="hello";  // 수정 불가 // 컴파일오류
	p[0] = 'H';  // 컴파일타임에 에러나기 위해

	printf("p : %s \n", p);

	return 0;
}

 


5. 배열의 경계를 넘어가는 문자열을 고정된 길이의 배열에 데이터를 저장하지말 것

- 배열의 크기를 넘는 문자열 입력은 버퍼 오버플로우 발생시킴

- 크기를 모르는 문자열을 고정된 크기의 배열에 바로 입력 받는 것은 위험

#define MAX_SZ 100
char tmp[MAX_SZ];    // 크기가 넉넉한 임시배열 생성
// Buf의 사이즈가 입력받은 문자열보다 작으면 다시 입력받기

do {
	printf(“input buf”);
	gets(tmp); // 임시배열에 받은 후
} while(strlen(tmp) >= sizeof(buf));  

// 임시배열을 버퍼에 복사
strcpy(buf, tmp);



6. strtok()에서 파싱되는 문자열은 보존되지 않음(알고 코딩해야 안전하다!)

- strtok()은 문자열을 구분자가 처음 나타나는 부분까지 파싱하고 구분자를 ‘\0’로 변경한 후 토큰의 처음 문자주소를 전달될 변수에 넣음

- 처음 나타난 구분자 위치에는 ‘\0’이 저장됨

-  문제점 : strtok()후 path가 변경됨

     2  #include <stdio.h>

     3  #include <string.h>

     4  #include <stdlib.h>

     5

     6  int main()

     7  {

     8     // strtok() 에서 파싱되는 문자열은 보존되지 않는다.

     9

    10     char *token;

    11     char path[30]="/usr/bin:/usr/sbin:/etc";

    12

    13     token=strtok(path, ":"); // 처음 호출 시 ":"찾아 '\0'로

    14     puts(token);

    15

    16     while(token=strtok(0,":"))

    17        puts(token);

    18

    19     printf("path: %s \n", path); //   /usr/bin 출력

    20

    21

    22     return 0;

    23  }

:!a.out

/usr/bin

/usr/sbin

/etc

path: /usr/bin

 

-  수정 : 원래의 문자열을 보존하려면 문자열을 버퍼에 복사한 후 복사된 버퍼를 strtok() 수행

     2  #include <stdio.h>

     3  #include <string.h>

     4  #include <stdlib.h>

     5

     6  int main()

     7  {

     8     // strtok() 에서 파싱되는 문자열은 보존되지 않는다.

     9

    10     char *token;

    11     char path[30]="/usr/bin:/usr/sbin:/etc";

    12     char PATH[30];

    13

    14     strcpy(PATH, path); // 복사해놓고시작

    15

    16     token=strtok(path, ":"); // 처음 호출 시 ":"찾아 '\0'로

    17     puts(token);

    18

    19     while(token=strtok(0,":"))

    20        puts(token);

    21

    22     printf("path: %s \n", path); //   /usr/bin 출력

    23     printf("path: %s \n", PATH); //   /usr/bin 출력

    24

    25     return 0;

    26  }

:!a.out

/usr/bin

/usr/sbin

/etc

path: /usr/bin

path: /usr/bin:/usr/sbin:/etc

 

 

 


 

 

 

 

 

 

궁금한 사항은 댓글로 남겨주세요💃💨💫
좋아요와 구독(로그인X)은 힘이 됩니다 🙈🙉

 

반응형
그리드형

댓글

❤️김세인트가 사랑으로 키웁니다❤️