const char * 배열 초기화 쉼표가없는 경우 컴파일러 경고 생성


53

C 코드에서 문자열 리터럴 테이블을 많이 사용하고 있습니다. 이 테이블은 모두 다음과 같이 다소 비슷합니다.

static const char* const stateNames[STATE_AMOUNT] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};

위의 코드의 문제점은 테이블이 길어지고 개발 중에 수정되면 때때로 쉼표를 잊어 버리는 것입니다. 코드는 쉼표가없는 문제없이 컴파일되지만 마지막 문자열이로 설정되면 프로그램이 중단됩니다 NULL. MinGW 및 Keil 컴파일러를 사용하여 확인했습니다.

쉼표가없는 경우 초기화에 대한 컴파일러 경고를 생성하는 방법이 있습니까?


1
이 테이블에 상태를 추가하는 것을 잊어 버리면 어떻게됩니까?
Jeroen3

1
@ Jeroen3 true 동일한 오류가 발생합니다. STATE_AMOUNT에 대해 목록 길이를 테스트하는 정적 어설 션을 사용하면이 문제도 해결됩니다.
Jonny Schubert

답변:


62

모든 포장 const char*다음 코드에서와 같이 괄호 한 쌍하면 문제를 해결해야한다 :

static const char* const stateNames[5] =
{
    ("Init state"),
    ("Run state"),
    ("Pause state")     //comma missing
    ("Pause state3"),
    ("Error state")
};

쉼표를 잊어 버린 경우 다음과 유사한 컴파일 오류가 발생합니다. error: called object is not a function or function pointer

라이브 데모


실제로 쉼표를 잊어 버린 경우 C는 실제로 다음 쉼표 또는 배열 끝까지 두 개 이상의 문자열을 연결합니다. 예를 들어 다음과 같이 쉼표를 잊어 버렸다고 가정 해 봅시다.

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state" //comma missing
    "Pause state3" //comma missing
    "Error state"
};

int main(void)
{  
    printf("%s\n", stateNames[0]);
    return 0;    
}

이것이 gcc-9.2생성하는 것입니다 (다른 컴파일러는 비슷한 코드를 생성합니다).

.LC0:
        .string "Init state"
        .string "Run state"
        .string "Pause statePause state3Error state" ; oooops look what happened
        .quad   .LC0
        .quad   .LC1
        .quad   .LC2
main:
        push    rbp
        mov     rbp, rsp
        mov     eax, OFFSET FLAT:.LC0
        mov     rdi, rax
        call    puts
        mov     eax, 0
        pop     rbp
        ret

마지막 세 문자열이 연결되고 배열이 예상 한 길이가 아닌 것이 분명합니다.


33

컴파일러가 배열을 계산하고 예기치 않은 결과가 발생하면 오류 메시지를 생성하도록 할 수 있습니다.

enum { STATE_AMOUNT = 4 };

static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state"    // <--- missing comma
    "Error state",
};

_Static_assert( sizeof stateNames / sizeof *stateNames == STATE_AMOUNT,
        "oops, missed a comma" );

_Static_assert컴파일러가 너무 오래되어 지원하지 않는 경우 구현할 아이디어는 이 스레드참조하십시오 .

보너스로, 새로운 상태를 추가 할 때 도움이 될 수 있지만 문자열 테이블을 업데이트하는 것을 잊어 버리십시오. 그러나 X 매크로도 살펴볼 수 있습니다.


젠장 .. 이것은 내가 방금 입력하려고했던 정확한 답이었다!
용접기

11

나는 항상 이것을 해결하기 위해 명시 적으로 크기가 지정된 배열에 대한 참조를 사용했습니다.

// no explicit size here
static const char* const stateNames[] =
{
    "Init state",
    "Run state",
    "Pause state",
    "Error state",
};
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;

http://coliru.stacked-crooked.com/a/593fc2eac80782a6

main.cpp:10:32: error: reference to type 'const char *const [5]' could not bind to an lvalue of type 'const char *const [4]'
static const char* const (&stateNameVerifier)[STATE_AMOUNT] = stateNames;

4
정적 어설 션은 훨씬 더 우아한 솔루션처럼 보입니다. 정적 어설 션이 언어의 일부로 구현되기 전에이 작업을 수행하는 습관이 있다고 가정합니다. 배열의 예상 크기를 확인하는 정적 어설 션에 비해 여전히 이점이 있습니까?
코디 그레이

2
@CodyGray : 예, 이것은 당신이 그것을 언급 한 지금 사전
정확한

9

이것은 컴파일러를 가져 오는 데 도움이되지 않지만 아래처럼 작성하면 인간이 쉼표를 쉽게 놓을 수 없습니다.

static const char* const stateNames[STATE_AMOUNT] =
{
      "Init state"
    , "Run state"
    , "Pause state"
    , "Error state"
};

3
끝에 무언가를 추가하는 것도 쉽습니다. 쉼표를 추가하기 위해 이전 줄을 편집 할 필요는 없습니다. (쉼표가 누락 된 주요 이유)
datafiddler

@datafiddler : 동의합니다. 또한 주석을 주석 처리하거나 주석 해제 할 때 SQL SELECT 명령에서 열 목록을 미세 조정하는 데 유용합니다. 당신은 종종 마지막 것을 바꾸고 싶어합니다. 첫 번째를 거의 바꾸고 싶지 않습니다. 이렇게하면 한 항목을 주석 처리하기 위해 여러 줄을 수정할 필요가 없습니다.
JonathanZ는
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.