ProgrammingC開発者

構造体を値渡しで関数に渡すと何が起こり、これがプログラムのパフォーマンスとセマンティクスにどのように影響するか?

Hintsage AIアシスタントで面接を突破

回答

C言語では、構造体を値渡しで関数に渡すと、構造体の完全なコピーが一時的なメモリ領域(通常は関数のスタック上)に作成されます。これは、関数内での変更が関数外の元の構造体に影響を与えないことを意味します。

大きな構造体を値渡しで渡す場合、すべてのメンバーをコピーする必要があるため、時間とメモリにコストがかかります。そのため、標準的な実践としては構造体のポインタを渡すことが推奨されます:

#include <stdio.h> struct Data { int arr[1000]; int flag; }; void modify_by_value(struct Data d) { d.flag = 10; // ローカルコピーのみが変更される } void modify_by_pointer(struct Data *d) { d->flag = 20; // オリジナルが変更される } int main() { struct Data data = { {0}, 0 }; modify_by_value(data); // data.flag == 0 modify_by_pointer(&data); // data.flag == 20 return 0; }

ポインタ渡しの利点:

  • 大量のデータをコピーするコストがない
  • 元の構造体を変更できる

値渡しの欠点:

  • コピーによる時間と空間のコスト
  • スタックの消費が増加する(特にマイコンでは)

ひねりのある質問

関数が構造体を値で返すとどうなるか?どんなリスクがあるか?

回答:

Cでは、関数から構造体を値で返すことができます。例えば:

struct Point { int x, y; }; struct Point make_point(int x, int y) { struct Point p = {x, y}; return p; // コピーが返される }

主なリスクはパフォーマンスです:構造体のコピーが作成され返されます。また、ローカル変数のアドレスを間違って返すと、構造体が不正なメモリを指す可能性があります:

struct Point* bad() { struct Point p = {1, 2}; return &p; // エラー:ローカル変数のアドレスを返している }

このテーマの詳細を知らないことによる実際のエラーの例


逸話

スタックが小さい組み込みデバイスで、開発者が大きな構造体(1KB)を値渡しで渡したため、スタックオーバーフローと断続的なシステムクラッシュが発生しました。調査の結果、各構造体のコピーが呼び出しの深いネストでスタックメモリ不足を引き起こしていることが判明しました。


逸話

企業のサーバーシステムで、プログラマがローカル構造体のポインタを返したため、関数から出た後に「ダングリング」ポインタへのアクセスがコードに発生しました。これが、製品環境でまれに再現されるクリティカルセグフォルトとして現れました。


逸話

オープンソースプロジェクトで、内部配列を持つ複雑な構造体を値で返す関数に切り替えた後、パフォーマンスが急激に低下しました。プロファイラは、構造体の不要な何度も繰り返されるコピーにかかるCPUの時間コストを明らかにしました。