Cの関数に引数として配列を渡す


89

配列を引数として含む関数を作成し、次のように配列の値を渡して呼び出します。

私が見つけたのは、arraytest()値を渡して関数を呼び出しているのに、の元のコピーint arr[]が変更されていることです。

その理由を教えてください。


1
参照によって配列を渡しますが、その内容を変更しています-したがって、データに変更が見られるのはなぜですか
Shaun Wilde 2011

main()を返す必要がありintます。
underscore_d

回答:


141

配列をパラメータとして渡す場合、これは

とまったく同じ意味

あなたがされているメインの値を変更します。

歴史的な理由から、配列は第一級市民ではなく、値で渡すことはできません。


3
どの表記法がどの状況でより良いですか?
ラモンマルティネス

22
@ Ramon-2番目のオプションを使用します。混乱が少なく、配列のコピーを取得していないことを示しているためです。
ボーパーソン2015

2
「歴史的理由」について説明していただけますか?値を渡すにはコピーが必要で、メモリの浪費になると思います。ありがとう
Jacquelyn.Marquardt 2016年

5
@ lucapozzobon-元々、Cには、単一の値を除いて、値による受け渡しがありませんでした。structこれが変更されたのは、言語に追加されるまではありませんでした。そして、配列のルールを変更するには遅すぎると考えられました。すでに数十人のユーザーがいました。:-)
Bo Persson

1
...などとまったく同じ意味です。void arraytest(int a[1000])ここで拡張された回答:stackoverflow.com/a/51527502/4561887
ガブリエルステープルズ2018

9

関数の引数として1次元配列渡したい場合は、次の3つの方法のいずれかで仮パラメーターを宣言する必要があります。3つの宣言方法はすべて、整数ポインターが実行されていることをコンパイラーに通知するため、同様の結果を生成します。受け取られる

したがって、元の値を変更しています。

ありがとう!!!


私はあなたの2番目の例を探していましたが、それぞれの方法の利点は何ですか?
パック

8

配列をコピーとして渡していない。これは、配列の最初の要素がメモリ内にあるアドレスを指すポインタにすぎません。


7

配列の最初の要素のアドレスを渡しています


7

Cの配列は、ほとんどの場合、配列自体の最初の要素へのポインターに変換されます。さらに詳細には、関数に渡される配列は常にポインターに変換されます。

ここにK&R2ndからの引用があります:

配列名が関数に渡されると、渡されるのは最初の要素の場所です。呼び出された関数内では、この引数はローカル変数であるため、配列名パラメーターはポインター、つまりアドレスを含む変数です。

書き込み:

書くことと同じ意味があります:

したがって、明示的に記述していなくても、ポインターを渡しているため、メインの値を変更していることになります。

詳細については、これを読むことをお勧めます。

さらに、SOに関する他の回答はここにあります


6

配列の最初のメンバーのメモリ位置の値を渡します。

したがって、関数内の配列の変更を開始すると、元の配列が変更されます。

それを忘れないでくださいa[1]です*(a+1)


1
* a + 1には()が欠けていると思います*(a + 1)
ShinTakezou 2011

私はCでプレーしたので、感謝を@Shin、しばらくして
アレックス

6

Cでは、いくつかの特殊なケースを除いて、配列参照は常に配列の最初の要素へのポインターに「減衰」します。したがって、配列を「値で」渡すことはできません。関数呼び出しの配列は、参照によって配列を渡すのと同様に、ポインターとして関数に渡されます。

編集:配列が最初の要素へのポインタに減衰しない3つの特別なケースがあります:

  1. sizeof aと同じではありませんsizeof (&a[0])
  2. &aと同じではありません&(&a[0])(そしてとはまったく同じではありません&a[0])。
  3. char b[] = "foo"と同じではありませんchar b[] = &("foo")

配列を関数に渡すと。たとえば、配列を作成し、int a[10]各要素にランダムな値を割り当てたとします。int y[]または、int y[10]またはを使用してこの配列を関数に渡すとint *y、その関数でsizeof(y)Answerを使用すると、バイトポインタが割り当てられます。したがって、この場合、ポインタとして減衰します。これも含めると役に立ちます。このpostimg.org/image/prhleuezdを
Suraj Jain

sizeof最初に定義した配列の関数でoperateを使用すると、配列として減衰しますが、他の関数を渡すと、sizeof演算子を使用してポインターとして減衰します。
Suraj Jain 2016


私はこれが古いことを知っています。誰かがこれを見た場合の2つの質問:)1。@ThomSmithは、それ&aが配列の&a[0]場合aとまったく同じではないと書いています。どうして?私のテストプログラムでは、配列が宣言されている関数と別の関数に渡されたときの両方で、両方が同じであることが示されています。2.作家は、「char b[] = "foo"は」と同じではないと書いていchar b[] = &("foo")ます。私にとって、後者はコンパイルすらしません。私だけでしょうか?
AvivCohn20年

6

多次元配列を引数として関数に渡します。 引数として1つのdim配列を渡すことは、多かれ少なかれ簡単です。2dim配列を渡すというより興味深いケースを見てみましょう。Cint **では、2つのdim配列の代わりにポインター構文()へのポインターを使用することはできません。例を見てみましょう:

ここでは、最初の引数として5つの整数の配列へのポインターを受け取る関数を指定しました。5列の2つのdim配列を引数として渡すことができます。

次のように、任意の2 dim配列を受け入れ、関数のシグネチャを変更できる、より一般的な関数を定義するというアイデアが浮かぶかもしれません。

このコードはコンパイルされますが、最初の関数と同じ方法で値を割り当てようとすると、ランタイムエラーが発生します。したがって、Cでは、多次元配列はポインタへのポインタと同じではありません...ポインタへのポインタ。Anint(*arr)[5]は、5つの要素の配列へのポインタです。int(*arr)[6] は6つの要素の配列へのポインター、そしてそれらは異なるタイプへのポインターです!

さて、高次元の関数引数を定義する方法は?シンプルで、パターンに従うだけです!これは、3次元の配列を取るように調整された同じ関数です。

ご想像のとおり、2番目の次元に4つの要素があり、3番目の次元に5つの要素がある3つのdim配列を引数として取ることができます。このようなものなら何でもOKです:

ただし、最初のサイズまでのすべての寸法サイズを指定する必要があります。


6

配列からptrへの自然な型の減衰を伴うCでの標準的な配列の使用法

@Bo Perssonは、ここで彼のすばらしい答えを正しく述べています

配列をパラメータとして渡す場合、これは

とまったく同じ意味

ただし、上記の2つの形式も追加します。

  1. とまったく同じ意味

  2. これはまったく同じ意味です

  3. これはまったく同じ意味です

  4. これはまったく同じ意味です

上記の配列例のすべてで、入力パラメーターの型はに減衰し、int *ビルドオプション-Wall -Wextra -Werrorがオンになっている場合でも、警告やエラーなしで呼び出すことができます(これらの3つのビルドオプションの詳細については、こちらのリポジトリを参照してください)。この:

実際のところ、「サイズ」の値([0][1][2][1000]配列パラメータの内部など)がここに美的/自己文書化のため、明らかばかりで、任意の正の整数を指定できます(size_tあなたが欲しいと思うタイプ)!

ただし、実際には、これを使用して、関数が受け取ると予想される配列の最小サイズを指定する必要があります。これにより、コードを作成するときに、追跡と検証が簡単になります。MISRA-C-2012規格(ご購入/ここ£15.00のために、標準の236-PG 2012バージョンのPDFをダウンロードし、これまでの状態(強調追加)へと行きます):

規則17.5配列型を持つと宣言されたパラメータに対応する関数の引数は、適切な数の要素を持たなければならない。

..。

パラメータが指定されたサイズの配列として宣言されている場合、各関数呼び出しの対応する引数は、少なくとも配列と同じ数の要素を持つオブジェクトを指している必要があります。

..。

関数パラメーターに配列宣言子を使用すると、ポインターを使用するよりも明確に関数インターフェースを指定できます。関数が期待する要素の最小数は明示的に示されていますが、これはポインターでは不可能です。

言い換えれば、C標準では技術的に強制されていなくても、明示的なサイズ形式を使用することをお勧めします。少なくとも、開発者としてのあなたや、コードを使用している他の人にとって、関数が期待するサイズ配列を明確にするのに役立ちます。あなたが渡す。


Cの配列に型安全性を強制する

@Winger Sendonが私の回答の下のコメントで指摘しているように、Cに配列タイプを配列サイズに基づいて異なるものとして処理させることができます

まず、あなたが使用して、その私の例では、単に上記認識する必要がありint array1[2];、次のように:arraytest(array1);原因array1自動的に崩壊しますint *。ただし、代わりにアドレス を取得してarray1呼び出すとarraytest(&array1)、まったく異なる動作が発生します。今、それは崩壊しませんint *!代わりに、の型は&array1ですint (*)[2]。これは、「intのサイズ2の配列へのポインタまたはint型のサイズ2の配列へのポインタ」を意味します。したがって、次のように、Cに配列の型安全性をチェックさせることができます。

この構文は読みにくいですが、関数ポインタの構文と似ています。オンラインツールcdeclは「intの配列2へのポインタとしてaを宣言する」2の配列へのポインタ)というint (*a)[2]意味を示しています。:括弧なしのバージョンでこれを混同しないでください、意味:「int型へのポインタの配列2として宣言する」(2つの配列のポインタへ)。intint * a[2]int

さて、この関数&は、入力パラメータとして正しいサイズの配列へのポインタを使用して、このようなアドレス演算子()で呼び出す必要があります!:

ただし、これにより警告が生成されます。

あなたはあり、ここで、このコードをテストします

Cコンパイラにこの警告をエラーに変換させ、常にarraytest(&array1);正しいサイズタイプ(int array1[2];この場合)の入力配列のみを使用して呼び出す必要があるようにする-Werrorには、ビルドオプションに追加します。onlinegdb.comで上記のテストコードを実行する場合は、右上の歯車アイコンをクリックし、[追加のコンパイラフラグ]をクリックしてこのオプションを入力します。ここで、次の警告が表示されます。

このビルドエラーに変わります:


次のように、特定のサイズの配列への「タイプセーフ」ポインタを作成することもできることに注意してください。

...しかし、これは必ずしもお勧めしません。言語構文の複雑さ、冗長性、コードの設計の難しさという非常に高いコストで、あらゆる場所で型の安全性を強制するために使用される多くのC ++の策略を思い出させてくれるので、私は嫌いです。これまでに何度も怒鳴りました(例:「C ++に関する私の考え」を参照)。


追加のテストと実験については、すぐ下のリンクも参照してください。

参考文献

上記のリンクを参照してください。また:

  1. オンラインでの私のコード実験:https//onlinegdb.com/B1RsrBDFD

2
void arraytest(int (*a)[1000])サイズが間違っているとコンパイラがエラーになるため、より良いです。
wingerse 2018

@WingerSendon、ここで確認する必要のある微妙な点がいくつかあり、構文が混乱していることを知っていたので(関数ptr構文が混乱しているように)、時間をかけて、最終的に、というタイトルの大きな新しいセクションで回答を更新しましたForcing type safety on arrays in C。ポイント。
ガブリエルステープルズ

@ GabrielStaples、ありがとう。あなたの答えはとても役に立ちます。この方法で高度なcを学ぶためのリファレンスを教えてもらえますか?
daryooosh

@daryooosh、残念ながら、私はできません。私は素晴らしい参考文献を持っていません。何年にもわたって深く掘り下げて、ここで少し、そこで少し拾いました。私にできる最善のことは、このように学んだことのいくつかを、ここのeRCaGuy_hello_worldリポジトリに時々ドロップすることです。ただし、上記で使用したCタイプの安全性のものは、慎重に使用する必要があることに注意してください。それはあなたのコードを複雑にし、可読性を1トン低下させ、それだけの価値はありません。可能な場合は単純な構文に焦点を合わせ、読みやすくします。
ガブリエルステープルズ

また、標準的な古典的なCの教科書はこのK&R The Cプログラミング言語の本です:en.wikipedia.org/wiki/The_C_Programming_Language
ガブリエルステープルズ

0

a[]または*a:を使用すると、配列は常に参照によって渡されます。


私はこれに賛成です。なぜ反対票を投じられたのかわかりません。
ガブリエルステープルズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.