Existem desvantagens em passar estruturas por valor em C, em vez de passar um ponteiro?
Se a estrutura é grande, obviamente existe o aspecto de desempenho de copiar muitos dados, mas para uma estrutura menor, deve basicamente ser o mesmo que passar vários valores para uma função.
Talvez seja ainda mais interessante quando usado como valores de retorno. C possui apenas valores de retorno únicos de funções, mas muitas vezes você precisa de vários. Portanto, uma solução simples é colocá-los em uma estrutura e retornar isso.
Existem razões a favor ou contra isso?
Como pode não ser óbvio para todos do que estou falando aqui, darei um exemplo simples.
Se estiver programando em C, mais cedo ou mais tarde você começará a escrever funções parecidas com esta:
void examine_data(const char *ptr, size_t len)
{
...
}
char *p = ...;
size_t l = ...;
examine_data(p, l);
Isto não é um problema. O único problema é que você precisa concordar com seu colega de trabalho na ordem em que os parâmetros devem ser, para que você use a mesma convenção em todas as funções.
Mas o que acontece quando você deseja retornar o mesmo tipo de informação? Você normalmente recebe algo parecido com isto:
char *get_data(size_t *len);
{
...
*len = ...datalen...;
return ...data...;
}
size_t len;
char *p = get_data(&len);
Isso funciona bem, mas é muito mais problemático. Um valor de retorno é um valor de retorno, exceto que nesta implementação não é. Não há como dizer, acima, que a função get_data não tem permissão para ver o que len aponta. E não há nada que faça o compilador verificar se um valor é realmente retornado por esse ponteiro. Então, no próximo mês, quando alguém modificar o código sem entendê-lo corretamente (porque ele não leu a documentação?), Ele será quebrado sem que ninguém perceba, ou começa a falhar aleatoriamente.
Então, a solução que proponho é a estrutura simples
struct blob { char *ptr; size_t len; }
Os exemplos podem ser reescritos assim:
void examine_data(const struct blob data)
{
... use data.tr and data.len ...
}
struct blob = { .ptr = ..., .len = ... };
examine_data(blob);
struct blob get_data(void);
{
...
return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();
Por alguma razão, acho que a maioria das pessoas instintivamente faria o examine_data levar um ponteiro para um blob de estrutura, mas não vejo o porquê. Ainda recebe um ponteiro e um número inteiro, é muito mais claro que eles andam juntos. E, no caso get_data, é impossível errar da maneira que descrevi antes, pois não há valor de entrada para o comprimento e deve haver um comprimento retornado.
fonte
void examine data(const struct blob)
está incorreto.gettimeofday
) usam ponteiros, e as pessoas tomam isso como exemplo.Respostas:
Para estruturas pequenas (por exemplo, point, rect), a passagem por valor é perfeitamente aceitável. Mas, além da velocidade, há outra razão pela qual você deve ter cuidado ao passar / retornar grandes estruturas por valor: espaço na pilha.
Muita programação C é para sistemas embarcados, nos quais a memória é premium e os tamanhos de pilha podem ser medidos em KB ou mesmo em bytes ... Se você estiver passando ou retornando estruturas por valor, cópias dessas estruturas serão colocadas em a pilha, potencialmente causando a situação que este site recebeu o nome de ...
Se eu vir um aplicativo que parece ter uso excessivo de pilha, as estruturas passadas por valor são uma das coisas que procuro primeiro.
fonte
Um motivo para não fazer isso que não foi mencionado é que isso pode causar um problema em que a compatibilidade binária é importante.
Dependendo do compilador usado, as estruturas podem ser passadas através da pilha ou registradores, dependendo das opções / implementação do compilador
Veja: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html
Se dois compiladores discordarem, as coisas podem explodir. Desnecessário dizer que os principais motivos para não fazer isso são ilustrados: consumo de pilha e desempenho.
fonte
int &bar() { int f; int &j(f); return j;};
Para realmente responder a essa pergunta, é preciso cavar fundo na área de montagem:
(O exemplo a seguir usa o gcc no x86_64. Qualquer pessoa pode adicionar outras arquiteturas como MSVC, ARM, etc.)
Vamos ter nosso programa de exemplo:
Compile-o com otimizações completas
Veja a montagem:
Isto é o que obtemos:
Excluindo os
nopl
pads, elegive_two_doubles()
possui 27 bytes egive_point()
29 bytes. Por outro lado,give_point()
produz menos uma instrução do quegive_two_doubles()
O interessante é que percebemos que o compilador foi capaz de otimizar
mov
as variantesmovapd
e mais rápidas do SSE2movsd
. Além disso,give_two_doubles()
na verdade , os dados entram e saem da memória, o que torna as coisas lentas.Aparentemente, muito disso pode não ser aplicável em ambientes incorporados (que é onde o campo de jogo para C é a maior parte do tempo atualmente). Como não sou um assistente de montagem, qualquer comentário será bem-vindo!
fonte
const
o tempo todo) e descobri que não há muita penalidade de desempenho (se não ganho) na cópia por valor , ao contrário do que muitos podem acreditar.A solução simples será retornar um código de erro como valor de retorno e tudo o mais como parâmetro na função.
Esse parâmetro pode ser uma estrutura, é claro, mas não vê nenhuma vantagem específica em passar isso por valor, basta enviar um ponteiro.
Passar estrutura por valor é perigoso, você precisa ter muito cuidado com o que está passando, lembre-se de que não há construtor de cópias em C, se um dos parâmetros da estrutura for um ponteiro, o valor do ponteiro será copiado, pode ser muito confuso e difícil de manter.
Só para completar a resposta (crédito total para Roddy ), o uso da pilha é outro motivo para não passar a estrutura por valor, acredite em mim, a depuração do estouro da pilha é PITA real.
Replay para comentar:
Passagem de struct por ponteiro, o que significa que alguma entidade possui uma propriedade sobre esse objeto e tem um conhecimento completo do que e quando deve ser liberado. Passar struct por valor cria uma referência oculta aos dados internos de struct (ponteiros para outras estruturas etc.). Isso é difícil de manter (possível, mas por quê?).
fonte
Uma coisa que as pessoas aqui se esqueceram de mencionar até agora (ou eu a ignorei) é que as estruturas geralmente têm um preenchimento!
Cada caractere é de 1 byte, cada curto é de 2 bytes. Qual é o tamanho da estrutura? Não, não são 6 bytes. Pelo menos não nos sistemas mais usados. Na maioria dos sistemas, será 8. O problema é que o alinhamento não é constante, é dependente do sistema, portanto a mesma estrutura terá alinhamento diferente e tamanhos diferentes em sistemas diferentes.
Não apenas esse preenchimento consome mais sua pilha, mas também aumenta a incerteza de não ser capaz de prever o preenchimento antecipadamente, a menos que você saiba como o sistema funciona e analise cada estrutura que você tem no seu aplicativo e calcule o tamanho por isso. Passar um ponteiro requer uma quantidade previsível de espaço - não há incerteza. O tamanho de um ponteiro é conhecido pelo sistema, é sempre igual, independentemente da aparência da estrutura e do tamanho do ponteiro sempre são escolhidos de maneira que estejam alinhados e não precisem de preenchimento.
fonte
Eu acho que sua pergunta resumiu as coisas muito bem.
Uma outra vantagem de passar estruturas por valor é que a propriedade da memória é explícita. Não há dúvida sobre se a estrutura é do heap e quem tem a responsabilidade de liberá-lo.
fonte
Eu diria que passar estruturas (não muito grandes) por valor, tanto como parâmetros quanto como valores de retorno, é uma técnica perfeitamente legítima. É preciso cuidar, é claro, de que a estrutura seja do tipo POD ou que a semântica da cópia seja bem especificada.
Atualização: desculpe, eu estava com meu limite de pensamento em C ++. Lembro-me de uma época em que não era legal em C retornar uma estrutura de uma função, mas isso provavelmente mudou desde então. Eu ainda diria que é válido desde que todos os compiladores que você espera usar suportem a prática.
fonte
Aqui está algo que ninguém mencionou:
Os membros de um
const struct
sãoconst
, mas se esse membro é um ponteiro (comochar *
), ele se tornachar *const
mais doconst char *
que realmente queremos. Obviamente, podemos assumir que aconst
documentação é de intenção e que qualquer pessoa que viole isso está escrevendo código incorreto (o que é), mas isso não é bom o suficiente para alguns (especialmente aqueles que passaram apenas quatro horas rastreando a causa de uma batida).A alternativa pode ser fazer um
struct const_blob { const char *c; size_t l }
e usá-lo, mas isso é bastante confuso - ele entra no mesmo problema de esquema de nomes que eu tenho comtypedef
os ponteiros. Assim, a maioria das pessoas prefere apenas ter dois parâmetros (ou, mais provavelmente neste caso, usar uma biblioteca de strings).fonte
struct const_blob
solução é que, mesmo que hajaconst_blob
membros que diferemblob
apenas da "constância indireta", os tiposstruct blob*
astruct const_blob*
serão considerados distintos para fins de uma regra estrita de alias. Consequentemente, se o código converter de ablob*
em aconst_blob*
, qualquer gravação subsequente na estrutura subjacente usando um tipo invalidará silenciosamente qualquer ponteiro existente do outro tipo, de modo que qualquer uso invoque o comportamento indefinido (que geralmente pode ser inofensivo, mas pode ser mortal) .A página 150 do Tutorial de montagem do PC em http://www.drpaulcarter.com/pcasm/ tem uma explicação clara sobre como C permite que uma função retorne uma estrutura:
Eu uso o seguinte código C para verificar a instrução acima:
Use "gcc -S" para gerar o assembly para este trecho de código C:
A pilha antes da chamada é criada:
A pilha logo após a chamada create:
fonte
Eu só quero apontar uma vantagem de passar suas estruturas por valor é que um compilador de otimização pode otimizar melhor seu código.
fonte