Funções retornando strings, bom estilo?

11

Nos meus programas em C, muitas vezes preciso de uma maneira de fazer uma representação em string dos meus ADTs. Mesmo que eu não precise imprimir a string para a tela de alguma forma, é interessante ter esse método para depuração. Portanto, esse tipo de função geralmente surge.

char * mytype_to_string( const mytype_t *t );

Realmente percebo que tenho (pelo menos) três opções aqui para manipular a memória para a sequência retornar.

Alternativa 1: Armazenando a sequência de retorno em uma matriz estática de caracteres na função Não preciso pensar muito, exceto que a corda é substituída a cada chamada. O que pode ser um problema em algumas ocasiões.

Alternativa 2: aloque a string na pilha com malloc dentro da função. Muito legal, pois não precisarei pensar no tamanho de um buffer ou na substituição. No entanto, eu tenho que lembrar de liberar () a string quando terminar, e também preciso atribuir a uma variável temporária para que eu possa liberar. e, em seguida, a alocação de heap é realmente muito mais lenta que a alocação de pilha; portanto, haverá um gargalo se isso for repetido em um loop.

Alternativa 3: Passe o ponteiro para um buffer e deixe o chamador alocar esse buffer. Gostar:

char * mytype_to_string( const mytype_t *mt, char *buf, size_t buflen ); 

Isso traz mais esforço para o chamador. Percebo também que essa alternativa me dá uma outra opção na ordem dos argumentos. Qual argumento devo ter primeiro e último? (na verdade seis possibilidades)

Então, qual devo preferir? Algum porque? Existe algum tipo de padrão não escrito entre os desenvolvedores C?

Øystein Schønning-Johansen
fonte
3
Apenas uma observação, a maioria dos sistemas operacionais usa a opção 3 - o chamador aloca buffer de qualquer maneira; informa ao ponteiro e capacidade do buffer; O receptor preenche o buffer e também retorna o comprimento real da string se o buffer for insuficiente. Exemplo: sysctlbynameno OS X e iOS
rwong 8/15

Respostas:

11

Os métodos que eu mais vi são 2 e 3.

O buffer fornecido pelo usuário é realmente bastante simples de usar:

char[128] buffer;
mytype_to_string(mt, buffer, 128);

Embora a maioria das implementações retorne a quantidade de buffer usada.

A opção 2 será mais lenta e é perigosa ao usar bibliotecas vinculadas dinamicamente, onde eles podem usar tempos de execução diferentes (e pilhas diferentes). Portanto, você não pode liberar o que foi alocado em outra biblioteca. Isso requer uma free_string(char*)função para lidar com isso.

catraca arrepiante
fonte
Obrigado! Acho que também gosto da Alternativa 3. No entanto, eu quero ser capaz de fazer coisas como: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));e, portanto, não gostaria de retornar o comprimento usado, mas o ponteiro para a string. O comentário da biblioteca dinâmica é realmente importante.
Øystein Schønning-Johansen
Isso não deveria ser sizeof(buffer) - 1para satisfazer o \0terminador?
9786 Michael-O
11
@ Michael-O no, o termo nulo é incluído no tamanho do buffer, o que significa que a string máxima que pode ser inserida é 1 menor que o tamanho passado. Esse é o padrão que a cadeia de caracteres segura funciona na biblioteca padrão como o snprintfuso.
Ratchet freak
@ratchetfreak Obrigado pelo esclarecimento. Seria bom estender a resposta com essa sabedoria.
9786 Michael-O
0

Ideia de design adicional para # 3

Quando possível, forneça também o tamanho máximo necessário para mytypeo mesmo arquivo .h que mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256

Agora o usuário pode codificar adequadamente.

char buf[MYTYPE_TO_STRING_SIZE];
puts(mytype_to_string(mt, buf, sizeof buf));

Ordem

O tamanho das matrizes, quando primeiro, permite tipos de VLA.

char * mytype_to_string( const mytype_t *mt, size_t bufsize, char *buf[bufsize]); 

Não é tão importante com uma dimensão única, mas útil com 2 ou mais.

void matrix(size_t row, size_t col, double matrix[row][col]);

Lembro-me de que a leitura do tamanho primeiro é o idioma preferido no próximo C. Precisa encontrar essa referência ...

chux - Restabelecer Monica
fonte
0

Como complemento à excelente resposta de @ ratchetfreak, eu indicaria que a alternativa nº 3 segue um paradigma / padrão semelhante ao das funções padrão da biblioteca C.

Por exemplo strncpy,.

char * strncpy ( char * destination, const char * source, size_t num );

Seguir o mesmo paradigma ajudaria a reduzir a carga cognitiva dos novos desenvolvedores (ou mesmo do seu futuro) quando eles precisarem usar sua função.

A única diferença com o que você tem em sua postagem seria que o destinationargumento nas bibliotecas C tende a ser listado primeiro na lista de argumentos. Então:

char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen ); 
John Go-Soco
fonte
-2

Além do fato de que você está propondo fazer um mau cheiro de código, a alternativa 3 me parece melhor. Também acho que @ gnasher729 você está usando a linguagem errada.

John Pfersich
fonte
O que exatamente você considera um cheiro de código? Por favor elabore.
Hulk
-3

Para ser sincero, convém mudar para um idioma diferente, no qual retornar uma string não é uma operação complexa, trabalhosa e propensa a erros.

Você pode considerar C ++ ou Objective-C, onde pode deixar 99% do seu código inalterado.

gnasher729
fonte