C언어 배열 초기화 함수와 주의사항

2021. 9. 28. 17:55 컴퓨터/프로그래밍

C언어 배열 초기화 함수 두 가지

C언어에서 배열을 초기화할 때 memset()bzero() 중 어떤 함수를 사용하시나요? bzero()가 간단해서 편하기도 하지만, 함수 이름이 뭘 하는지 명확해서 소스 코드를 이해하는데 도움을 줍니다. 그러나 이런 장점이 있다고 해도 memset()을 사용하는 것이 좋습니다. 왜일까요? bzero()가 "deprecated 함수"이기 때문인데요, deprecated의 뜻이 "더 이상 사용되지 않는"다는 뜻을 보더라도 하위 호환을 위해 제공되는 것이어서 bzero()보다는 memset()을 사용하는 것을 권합니다.

소프트웨어 기능 중에 "be deprecated"로 언급되어 있다면 더 좋은 것이 나와서 앞으로 이 기능이 없어질지 모른다는 뜻으로 쓰인다는 것을 생각하면 이해하기 쉬운데요, 계속 사용할 수 있어도 매우 친절하게 만들어진 컴파일러라면 계속해서 warring으로 귀찮게 할지도 모릅니다. "deprecated"에 대해서는 아래에 좀 더 자세히 다루기로 하겠습니다.

더욱이 bzero()는 비표준 함수인데요, 그렇다고 0으로 초기화하는데 굳이 0을 넣어야 하나 귀찮다면 두 가지 방법이 있습니다.

첫 번째 방법은 변수를 선언할 때 0으로 초기화할 수 있습니다.

int num[10] = {0};

       or
       
int num[10] = {0,};

bzero()보다도 간단하지만, 선언할 때만 가능합니다.

int num[10];

printf( "%d\n", num[0]);
num = { 0 };

위와 같이 실행 중간에 초기화할 수 없습니다. 이런 제약이 있지만, 배열을 선언할 때 초기화 방법으로 매우 유용합니다. 값을 알고 있는 요소만 지정하고 생각할 수 없는 나머지는 0으로 초기화를 할 수 있습니다. 예를 들어 num[0]과 num[1]에는 각각 1000, 2000을 넣고 나머지는 0으로 초기화하고 싶다면 아래와 같이 한 줄로 처리할 수 있습니다.

int num[1000] = { 1000, 2000};

두 번째 방법은 ZeroMemory()를 사용하는 것입니다.

ZeroMemory( num, 10);

bzero()처럼 변수명과 배열 크기만 넣어 주면 되며 함수 이름이 기능을 명확히 알려 주어서 읽기 편한 코드를 만들어 줍니다. 그러나 안타깝게도 윈도우 API에서 제공하는 함수입니다. 또한, memset()을 호출하는 매크로 함수입니다.

그렇다면? 네, 플랫폼에 관계없는 프로그램을 작성하려면 역시 memset()을 사용하는 것이 좋습니다. 잔뜩 ZeroMemory()를 넣었다가 리눅스에서 사용해야 한다면 무료하게 memset()으로 바꾸는 작업을 해야 합니다. 반드시 윈도우에서만 작업한다고 해도 습관을 한쪽에만 맞추는 것은 좋은 것이 못 됩니다.

deprecated 코딩

deprecated가 더 이상 사용하지 않는다는 뜻이지만, 이상하지요? 지금 사용하고 있는데? 처음 "deprecated 함수"를 알게 되었을 때 뭔가 이상해도 한참 이상했습니다. 하위 호환을 위해 제공한다면 앞으로 언젠가는 안 하겠다는 것인가? 그러나 "deprecated"가 함수에만 국한된 것이 아니라서 앞으로 계속 사용할 수 있을지 장담할 수 없다고 이해하는 것보다는 안전하지 못한 방법이니 다르게 코딩하라는 뜻으로 이해하는 것이 좋겠습니다.

즉, 프로그래밍에서 deprecated 경고는 함수뿐만 아니라 코딩 방식에서도 발생합니다. 예를 들어 아래의 코드는 deprecated 된 방법입니다.

int main( void){
    char *ptr= "badayak.com";

    printf( "%s\n", ptr);
}

C언어 책에서 쉽게 볼 수 있는 예제인데요, 책에서 소개하는 내용이라서 아무런 의심 없이 학습하게 됩니다. 그러나 안전한 코드가 못 되며 "더 이상 사용하지 않을 것을 권하는 불안한" 코드입니다. 컴파일러에 따라서는 아무런 warring을 내지 않는 코드이지만, 아래와 같이 한 줄만 넣어도 실행 에러가 발생합니다.

int main( void){
    char *ptr= "badayak.com";
    *ptr = 'B';
    printf( "%s\n", ptr);
}

"badayak.com"의 첫 번째 문자를 대문자로 바꾸려 한 것인데요, 포인터 *ptr에 대입되는 "badayak.com"은 애플리케이션 영역에 있으므로 변경할 수 없습니다. 즉, *ptr = 'B'; 는 컴파일 에러가 없고 warring도 없지만, 실행할 수 없는 코드여서 실행하면 "세그멘테이션 오류"가 발생합니다.

조금 더 자세히 말씀드려서 포인터 변수 ptr은 변수 영역에 생성되지만, "badayak.com"은 프로그램이 실행할 때 메모리에 올려지는 애플리케이션 영역에 있으며, 애플리케이션 영역은 수정할 수 없도록 보호됩니다. 그 애플리케이션 영역의 메모리를 'b'에서 'B'로 변경하려고 하니 실행 오류가 발생하게 됩니다.

결국 안전하지 못한 코드가 될 수 있어서 꼼꼼하고 친절한(?) 컴파일러이라면, 컴파일할 때에 변수 선언하는 char *ptr= "badayak.com"; 부터 아래와 같이 경고하게 됩니다.

deprecated conversion from string constant to 'char*'

그렇다면 어떻게 해야 할까요? const 키워드로 ptr의 내용이 상수임을 정확히 명시하면 됩니다.

int main( void){
    const char *ptr= "badayak.com";
    *ptr = 'B';
    printf( "%s\n", ptr);
}

이렇게 코딩하면 deprecated 경고는 없어지고 *ptr = 'B'; 는 컴파일 오류가 발생하게 되어 실행 오류를 발생하는 코드를 사전에 제거할 수 있습니다.

참고로 위 코드를 에러 없이 실행하게 하려면 포인터 변수를 배열 변수로 바꾸어 주면 됩니다.

int main( void){
    char ptr[]= "badayak.com";
    ptr[0] = 'B';
    printf( "%s\n", ptr);
}

*를 []로만 바꾸었습니다만, 실행까지 아주 잘 됩니다. 이유는 포인터를 배열로 선언하면 배열 변수는 애플리케이션 영역에 있는 "badayak.com" 문자열 개수만큼 변수 영역에 생성되고 "badayak.com"로 초기화해 주기 때문에 첫 번째 문자뿐만 아니라 모든 요소를 변경할 수 있습니다.

즉, 앞서 *ptr은 애플리케이션 영역을 가리키고 ptr[]은 변수 영역에 있는 변수라서 값을 바꾸는 것은 자연스러운 일입니다.

C언어, 강력하면서도 많이 혼란스럽죠? C언어의 포인터와 배열에 대한 자유로운 코딩 방법을 제공한 것이 혼란스럽게 만들기 때문입니다. 위 코드는 아래와 같이도 작성할 수 있습니다.

int main( void){
    char ptr[] = "badayak.com";

    *ptr = 'B';
    printf( "%s\n", ptr);
}

ptr[0] = *ptr = *(ptr+0) = 'B' 때문에 C언어의 포인터와 배열은 같다고 생각하는 분들도 있습니다. 그러나 배열을 뜻하는 [] 키워드가 C언어는 연산자라서 가능한 코딩이라서 배열과 포인터가 같다고 생각해서는 안 됩니다.

이 댓글을 비밀 댓글로

티스토리 로그인이 풀리면 여기를 클릭하세요.

  1. c언어 오랜만에 보네요~^^ 좋은정보 잘보고 갑니다~^^
error: Content is protected !!