Está usando membros da matriz flexível em C má prática?

Recentemente, li que usar membros de matriz flexível em C era uma prática de engenharia de software ruim. No entanto, essa afirmação não foi apoiada por nenhum argumento. Isso é um fato aceito?

( Os membros da matriz flexível são um recurso C introduzido em C99, pelo qual é possível declarar o último elemento como uma matriz de tamanho não especificado. Por exemplo:)

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

    É um “fato” aceito que usar o goto é uma prática ruim de engenharia de software. Isso não é verdade. Há momentos em que o goto é útil, particularmente ao lidar com a limpeza e ao portar do montador.

    Os membros flexíveis da matriz parecem ter um uso principal, que é o mapeamento de formatos de dados legados, como os formatos de gabaritos de janela no RiscOS. Eles teriam sido extremamente úteis para isso cerca de 15 anos atrás, e tenho certeza de que ainda existem pessoas por aí lidando com tais coisas que poderiam considerá-las úteis.

    Se o uso de membros da matriz flexível é má prática, então sugiro que todos nós vamos dizer aos autores do C99 especifique isso. Eu suspeito que eles possam ter uma resposta diferente.

    A razão que eu daria para não fazer isso é que não vale a pena amarrar seu código ao C99 apenas para usar esse recurso.

    O ponto é que você sempre pode usar o seguinte idioma:

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

    Isso é totalmente portátil. Então você pode levar em conta o 1 ao alocar a memory para n elementos nos data da matriz:

     ptr = malloc(sizeof(struct header) + (n-1)); 

    Se você já tem o C99 como requisito para construir seu código por qualquer outro motivo ou se você for alvo de um compilador específico, não vejo nenhum dano.

    Você quis dizer…

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

    Em C, esse é um idioma comum. Eu acho que muitos compiladores também aceitam:

      unsigned char data[0]; 

    Sim, é perigoso, mas, novamente, não é mais perigoso que matrizes C normais – ou seja, MUITO perigoso ;-). Use-o com cuidado e somente em circunstâncias em que você realmente precise de uma variedade de tamanho desconhecido. Certifique-se de malloc e liberar a memory corretamente, usando algo como: –

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

    Uma alternativa é tornar os dados apenas um ponteiro para os elementos. Você pode então realloc () dados para o tamanho correto, conforme necessário.

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

    Claro, se você estivesse perguntando sobre o C ++, qualquer um desses seria uma prática ruim. Então você normalmente usa vetores STL.

    Eu vi algo assim: da interface C e implementação.

      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! 

    Nota: os dados não precisam ser o último membro.

    Como uma nota lateral, para compatibilidade de C89, tal estrutura deveria ser alocada como:

     struct header *my_header = malloc(offsetof(struct header, data) + n * sizeof my_header->data); 

    Ou com macros:

     #define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */ #define SIZEOF_FLEXIBLE(type, member, length) \ ( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] ) struct header { size_t len; unsigned char data[FLEXIBLE_SIZE]; }; ... size_t n = 123; struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n)); 

    A configuração de FLEXIBLE_SIZE para SIZE_MAX quase garante que isso falhará:

     struct header *my_header = malloc(sizeof *my_header); 

    Existem algumas desvantagens relacionadas a como as estruturas são usadas às vezes e pode ser perigoso se você não pensar nas implicações.

    Por exemplo, se você iniciar uma function:

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

    Em seguida, os resultados são indefinidos (já que nenhum armazenamento foi alocado para dados). Isso é algo que você normalmente estará ciente, mas há casos em que os programadores C provavelmente estão acostumados a usar a semântica de valor para estruturas, que é dividida de várias outras maneiras.

    Por exemplo, se eu definir:

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

    Então eu posso copiar duas instâncias simplesmente por atribuição, ou seja:

     struct header2 inst1 = inst2; 

    Ou se eles são definidos como pointers:

     struct header2 *inst1 = *inst2; 

    No entanto, isso não funcionará, já que os data matriz da variável não são copiados. O que você quer é dinamicamente malloc o tamanho da estrutura e copiar sobre a matriz com memcpy ou equivalente.

    Da mesma forma, escrever uma function que aceite uma struct não funcionará, já que os argumentos em chamadas de function são, novamente, copiados por valor e, portanto, o que você obterá provavelmente é apenas o primeiro elemento de seus data matriz.

    Isso não faz com que seja uma má ideia usar, mas você deve se lembrar de sempre alocar dinamicamente essas estruturas e apenas passá-las como pointers.

    Não, usar membros de matriz flexíveis em C não é uma prática ruim.

    Este recurso de linguagem foi padronizado pela primeira vez na ISO C99, 6.7.2.1 (16). Para o padrão atual, ISO C11, é especificado na Seção 6.7.2.1 (18).

    Você pode usá-los assim:

     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; 

    Alternativamente, você pode alocá-lo assim:

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

    Observe que sizeof(Header) inclui eventuais bytes de preenchimento, portanto, a alocação a seguir está incorreta e pode gerar um estouro de buffer:

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

    Uma estrutura com um array flexível reduz o número de alocações para ele em 1/2, ou seja, em vez de 2 alocações para um object de estrutura você precisa apenas de 1. Assim, se você tem que alocar um grande número de instâncias de struct você melhora mensuravelmente o tempo de execução do seu programa (por um fator constante).

    Em contraste com isso, usar construções não padronizadas para membros de matriz flexíveis que geram comportamento indefinido (por exemplo, em long v[0]; ou long v[1]; ) obviamente é uma prática ruim. Assim, como qualquer comportamento indefinido, isso deve ser evitado.

    Desde que a ISO C99 foi lançada em 1999, quase 20 anos atrás, a compatibilidade com ISO C89 é um argumento fraco.