programing

포인터를 거부하는 배열 크기 매크로

procenter 2022. 8. 17. 23:20
반응형

포인터를 거부하는 배열 크기 매크로

자주 학습되는 표준 어레이 크기 매크로는 다음과 같습니다.

#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))

혹은 그에 상당하는 구성일 수도 있어요그러나 포인터가 들어가면 묵묵히 성공하고, 신기하게 무너질 때까지 런타임에 그럴듯하게 보일 수 있는 결과를 얻을 수 있다.

로컬 어레이 변수를 가진 함수를 리팩터링하여 어레이를 파라미터로 하는 새로운 함수로 어레이 조작의 비트를 이동합니다.

그래서 질문은 다음과 같습니다: "위생적인" 매크로가 오남용을 탐지할 수 있는가?ARRAYSIZEC의 매크로를 컴파일 시에 사용하는 것이 바람직합니다.C++에서는 어레이 인수 전용 템플릿을 사용합니다.C에서는 어레이와 포인터를 구별할 수 있는 방법이 필요할 것 같습니다.(예를 들어 어레이를 거부하려면 예를 들어 다음과 같이 합니다. (arr=arr, ...)어레이 할당이 불법이기 때문입니다).

Linux 커널은 다음과 같은 적절한 구현을 사용합니다.ARRAY_SIZE이 문제에 대처하기 위해서:

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))

와 함께

#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))

그리고.

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

물론 이것은 2개의 instrinics를 사용하기 때문에 GNU C에서만 이식할 수 있습니다.typeof오퍼레이터와__builtin_types_compatible_p기능.또한 그들의 "유명한"을 사용한다.BUILD_BUG_ON_ZEROGNU C에서만 유효한 매크로입니다.

컴파일 시간 평가 요건(당사가 원하는 것)을 가정했을 때, 저는 이 매크로의 휴대용 구현을 모릅니다.

「반 포터블」의 실장은, 이하와 같습니다(일부 케이스는 대상이 되지 않습니다).

#define ARRAY_SIZE(arr)  \
    (sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr)))

와 함께

#define IS_ARRAY(arr)  ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e)  \
    (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))

와 함께gcc인수가 의 배열인 경우 경고를 주지 않습니다.-std=c99 -Wall그렇지만-pedantic경고를 줄 것이다.이유는IS_ARRAYexpression은 정수 정수 표현식이 아닙니다(포인터 유형 및 첨자 연산자는 정수 정수 정수 표현식에 사용할 수 없습니다). 및 비트 필드 폭은 다음과 같습니다.STATIC_EXP에는 정수 상수식이 필요합니다.

의 이 버전ARRAYSIZE()돌아온다0언제arr포인터이며 순수 배열일 때의 크기입니다.

#include <stdio.h>

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)

int main(void)
{
    int a[5];
    int *b = a;
    int n = 10;
    int c[n]; /* a VLA */

    printf("%zu\n", ARRAYSIZE(a));
    printf("%zu\n", ARRAYSIZE(b));
    printf("%zu\n", ARRAYSIZE(c));
    return 0;
}

출력:

5
0
10

Ben Jackson이 지적한 바와 같이 런타임 예외를 강제로 적용할 수 있습니다(0으로 분할).

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))

유감스럽게도 컴파일 시간 오류(의 주소)를 강제할 수 없습니다.arg런타임에 비교해야 합니다.)

컬렉션의 또 다른 예시입니다.

#define LENGTHOF(X) ({ \
    const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
    typeof(X[0]) (*should_be_an_array)[length] = &X; \
    length; })

장점:

  1. 표준 어레이, 가변길이 어레이, 다차원 어레이, 제로사이즈 구조의 어레이에서 동작합니다.
  2. 포인터, 구조체 또는 결합을 전달하면 컴파일 오류(경고가 아님)가 발생합니다.
  3. C11의 기능에 의존하지 않습니다.
  4. 매우 읽기 쉬운 오류를 발생시킵니다.

단점:

  1. 다음 중 몇 가지 gcc 확장에 따라 달라집니다.Type of, Statement Exprs 및 (필요한 경우) 조건
  2. C99 VLA 기능에 따라 다릅니다.

C11을 사용하면 어레이와 포인터를 구분할 수 있습니다._Generic단, 이 방법은 요소 유형을 제공하는 경우에만 찾을 수단은 다음과 같습니다.

#define ARRAY_SIZE(A, T) \
    _Generic(&(A), \
            T **: (void)0, \
            default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0])))


int a[2];
printf("%zu\n", ARRAY_SIZE(a, int));

매크로 체크: 1) A 포인터가 포인터 투 포인터가 아닙니다.2) Elem 포인터는 T 포인터입니다.평가 대상:(void)0포인터에서는 정적으로 실패합니다.

불완전한 답변이지만, 독자라면 개선해서 그 타입의 파라미터를 없앨 수 있을지도 모릅니다.

유형 매개 변수 대신 유형 매개 변수를 사용하여 bluss의 답변 수정:

#define ARRAY_SIZE(A) \
    _Generic(&(A), \
    typeof((A)[0]) **: (void)0, \
    default: sizeof(A) / sizeof((A)[0]))

끔찍해, 그래, 하지만 그건 효과가 있고 휴대할 수 있어.

#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \
                       (sizeof(arr)/sizeof(*arr)) : \
                       -1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__))

컴파일 시 아무것도 검출되지 않지만 에러 메시지가 출력됩니다.stderr및 반환-1포인터인지 배열 길이가 1인지 확인합니다.

==> 데모 <==>

으로 gcc 타입의 확장에 의존하는 것을 나타냅니다.

#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \
                         sizeof(arr) / sizeof(arr[0]);})

이는 동일한 개체를 설정하고 어레이 지정 이니셜라이저를 사용하여 초기화함으로써 작동합니다.어레이가 통과하면 컴파일러는 만족합니다.포인터가 전달되면 컴파일러는 다음과 같이 불만을 제기합니다.

arraysize.c: In function 'main':
arraysize.c:11: error: array index in non-array initializer
arraysize.c:11: error: (near initialization for 'p_is_a_pointer')

다음은 GNU 확장자를 사용하는 스테이트먼트 식이라고 하는1가지 가능한 해결책입니다.

#define ARRAYSIZE(arr) \
    ({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \
     sizeof(arr) / sizeof((arr)[0]);})

이것은 정적 어설션을 사용하여 다음과 같이 주장합니다.sizeof(arr) != sizeof(void*)여기에는 분명한 제한이 있습니다.이 매크로는 크기가 정확히1개의 포인터(예를 들어 포인터/정수의 1장 배열, 32비트 플랫폼상의 4장 바이트 배열)에서는 사용할 수 없습니다.그러나 이러한 특정 인스턴스는 충분히 쉽게 해결할 수 있습니다.

이 솔루션은 이 GNU 확장을 지원하지 않는 플랫폼에는 이식할 수 없습니다.그런 경우에는 표준 매크로만 사용하고 실수로 매크로에 포인터가 전달될 염려가 없습니다.

제가 개인적으로 좋아하는 gcc 4.6.3과 4.9.2:

#define STR_(tokens) # tokens

#define ARRAY_SIZE(array) \
    ({ \
        _Static_assert \
        ( \
            ! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), \
            "ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" \
        ); \
        sizeof(array) / sizeof((array)[0]); \
    })

/*
 * example
 */

#define not_an_array ((char const *) "not an array")

int main () {
    return ARRAY_SIZE(not_an_array);
}

컴파일러 인쇄

x.c:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array"

언급URL : https://stackoverflow.com/questions/19452971/array-size-macro-that-rejects-pointers

반응형