유연한 배열 구성원을 C 나쁜 습관으로 사용하고 있습니까?




arrays data-structures (5)

goto를 사용하면 소프트웨어 엔지니어링 실천이 좋지 않다는 사실이 받아 들여지는 "사실"입니다. 그건 사실이 아니야. goto가 유용 할 때가 있으며, 특히 정리를 처리하거나 어셈블러에서 이식 할 때 유용합니다.

유연한 배열 구성원은 RiscOS의 창 템플릿 형식과 같은 레거시 데이터 형식을 매핑하는 내 머리 꼭대기에서 한 가지 주요 용도로 사용합니다. 그들은 약 15 년 전에 이것에 대해 매우 유용했을 것이고, 사람들을 유용하게 사용할 수있는 그런 것들을 다루는 사람들이 아직도 있다고 확신합니다.

유연한 배열 구성원을 사용하는 것이 나쁜 습관이라면 나는 우리 모두가 C99 사양의 저자에게이 사실을 알리라고 제안한다. 나는 그들이 다른 대답을 가지고 있을지도 모른다고 생각한다.

나는 최근에 C에서 유연한 배열 멤버를 사용하는 것이 불쌍한 소프트웨어 엔지니어링 관행이라고 읽었다. 그러나 그 성명서는 어떤 주장도 뒷받침되지 않았습니다. 이것은 받아 들여진 사실입니까?

( 유연한 배열 멤버 는 C99에서 소개 된 C 기능으로 마지막 요소를 지정되지 않은 크기의 배열로 선언 할 수 있습니다. 예를 들면 다음과 같습니다.)

struct header {
    size_t len;
    unsigned char data[];
};

구조체가 사용되는 방법과 관련하여 몇 가지 단점이 있으며, 그 의미를 생각하지 않으면 위험 할 수 있습니다.

예를 들어, 함수를 시작하면 다음과 같습니다.

void test(void) {
  struct header;
  char *p = &header.data[0];

  ...
}

그런 다음 결과는 정의되지 않습니다 (데이터에 대해 스토리지가 할당되지 않았기 때문에). 이것은 당신이 일반적으로 알고있을 것입니다 만, C 프로그래머가 구조체에 대한 가치 의미론을 사용할 수있는 경우가 있습니다. 구조체는 여러 가지 다른 방법으로 분해됩니다.

예를 들어, 내가 정의한 경우 :

struct header2 {
  int len;
  char data[MAXLEN]; /* MAXLEN some appropriately large number */
}

그러면 할당을 통해 간단히 두 개의 인스턴스를 복사 할 수 있습니다.

struct header2 inst1 = inst2;

또는 포인터로 정의 된 경우 :

struct header2 *inst1 = *inst2;

그러나 가변 배열 data 는 복사되지 않으므로 작동하지 않습니다. 원하는 것은 구조체의 크기를 동적으로 malloc하여 memcpy 또는 그와 동등한 배열로 복사하는 것입니다.

마찬가지로 구조체를 받아들이는 함수를 작성하는 것은 작동하지 않습니다. 왜냐하면 함수 호출의 인수가 다시 값으로 복사되기 때문에 얻을 수있는 것은 배열 data 의 첫 번째 요소 일뿐입니다.

이것은 사용하는 것은 나쁜 생각은 아니지만 항상 구조체를 동적으로 할당하고 포인터로만 전달하는 것을 명심해야합니다.


나는 C 인터페이스와 구현에서 이런 것을 보았다.

  struct header {
    size_t len;
    unsigned char *data;
};

   struct header *p;
   p = malloc(sizeof(*p) + len + 1 );
   p->data = (unsigned char*) (p + 1 );  // memory after p is mine! 

참고 : 데이터는 마지막 멤버 일 필요는 없습니다.


너 ...

struct header
{
 size_t len;
 unsigned char data[];
}; 

C에서는 일반적인 숙어입니다. 나는 많은 컴파일러가 받아 들일 것이라고 생각한다.

  unsigned char data[0];

예, 그것은 위험합니다,하지만 다시, 그것은 정상적인 C 배열보다 위험합니다. 즉 매우 위험합니다. 신중하게 배열을 알 수없는 크기의 배열이 정말로 필요한 상황에서만 사용하십시오. 다음과 같은 것을 사용하여 malloc과 메모리를 올바르게 해제했는지 확인하십시오.

  foo = malloc(sizeof(header) + N * sizeof(data[0]));
  foo->len = N;

대안은 데이터를 요소에 대한 포인터로 만드는 것입니다. 그런 다음 필요에 따라 올바른 크기로 데이터를 다시 할당 할 수 있습니다.

  struct header
    {
     size_t len;
     unsigned char *data;
    }; 

물론, C ++에 대해 묻는다면, 이것들 중 하나는 나쁜 습관이 될 것입니다. 그런 다음 일반적으로 대신 STL 벡터를 사용합니다.


아니요, C에서 유연한 배열 멤버 를 사용하는 것은 나쁜 습관이 아닙니다.

이 언어 기능은 ISO C99, 6.7.2.1 (16)에서 처음 표준화되었습니다. 현재 표준 인 ISO C11의 경우 6.7.2.1 절 (18)에 명시되어 있습니다.

다음과 같이 사용할 수 있습니다.

struct Header {
    size_t d;
    long v[];
};
typedef struct Header Header;
size_t n = 123; // can dynamically change during program execution
// ...
Header *h = malloc(sizeof(Header) + sizeof(long[n]));
h->n = n;

또는 다음과 같이 할당 할 수 있습니다.

Header *h = malloc(sizeof *h + n * sizeof h->v[0]);

sizeof(Header) 에는 최종 패딩 바이트가 포함되어 있으므로 다음 할당이 잘못되어 버퍼 오버플로가 발생할 수 있습니다.

Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid!

가변 배열 멤버가있는 구조체는 할당량을 1/2로 줄입니다. 즉, 하나의 struct 객체에 대해 2 개의 할당 대신 1 개만 필요합니다. 따라서 구조체 인스턴스를 많이 할당해야하는 경우 프로그램의 실행 시간 (일정한 요소로).

이와 달리, 정의되지 않은 동작 (예 : long v[0]; 또는 long v[1]; )을 생성하는 유연한 배열 구성원에 대해 비표준 구조를 사용하면 분명히 나쁜 습관이됩니다. 따라서 정의되지 않은 동작처럼이 작업은 피해야합니다.

거의 20 년 전에 ISO C99가 1999 년에 발표 되었기 때문에 ISO C89 호환성을 위해 노력하는 것은 약한 주장입니다.







flexible-array-member