web-dev-qa-db-ja.com

数値の二乗を計算するこの方法を理解できません

私は数の二乗を計算する関数を見つけました:

_int p(int n) {
    int a[n]; //works on C99 and above
    return (&a)[n] - a;
}
_

Nの値を返します2。質問は、それをどのように行うのですか?少しテストした後、_(&a)[k]_と_(&a)[k+1]_の間がsizeof(a)/sizeof(int)であることがわかりました。何故ですか?

135
Emanuel

明らかにハック...ですが、*演算子を使用せずに数値を二乗する方法です(これはコーディングコンテストの要件でした)。

(&a)[n] 

ロケーションのintへのポインターと同等です

(a + sizeof(a[n])*n)

したがって、式全体は

  (&a)[n] -a 

= (a + sizeof(a[n])*n -a) /sizeof(int)

= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n
117
Mark Lakata

このハックを理解するには、まずポインターの違いを理解する必要があります。つまり、同じ配列の要素を指す2つのポインターを減算するとどうなりますか?

あるポインターを別のポインターから減算すると、結果はポインター間の距離(配列要素で測定)になります。したがって、pが_a[i]_を指し、qが_a[j]_を指す場合、_p - q_は_i - j_と等しくなります。 。

C11:6.5.6加算演算子(p9):

2つのポインターが減算されるの場合、両方は同じ配列オブジェクトの要素、または配列オブジェクトの最後の要素の1つを指します。 結果は2つの配列要素の添え字の差です。 [...]。
つまり、式PQがそれぞれ配列オブジェクトのi- thとj- thの要素を指す場合、 式_(P)-(Q)_の値は_i−j_で、値がタイプ_ptrdiff_t_のオブジェクトに適合する場合。

配列名のポインターへの変換を知っていることを期待しています。aは配列の最初の要素へのポインターaに変換します。 _&a_はメモリブロック全体のアドレスです。つまり、配列aのアドレスです。以下の図は、理解するのに役立ちます(詳細な説明については、 この回答 をお読みください):

enter image description here

これは、なぜaと_&a_のアドレスが同じであり、_(&a)[i]_がiのアドレスである理由を理解するのに役立ちます番目 配列(aと同じサイズ)。

だから、声明

_return (&a)[n] - a; 
_

と同等です

_return (&a)[n] - (&a)[0];  
_

この違いにより、ポインター_(&a)[n]_と_(&a)[0]_の間の要素数が得られます。これらは、それぞれnn要素のint配列です。したがって、配列要素の合計は_n*n_ = nです。2


注:

C11:6.5.6加算演算子(p9):

2つのポインターが減算されると、両方が同じ配列オブジェクトの要素、または配列オブジェクトの最後の要素の1つを指す;結果は、2つの配列要素の添え字の差です。 結果のサイズはimplementation-definedで、そのタイプ(符号付き整数型)は_ptrdiff_t_ヘッダーで定義された_<stddef.h>_です。結果がそのタイプのオブジェクトで表現できない場合、動作は未定義です。

_(&a)[n]_は、同じ配列オブジェクトの要素も配列オブジェクトの最後の要素の過去の要素も指さないため、_(&a)[n] - a_は未定義の動作を呼び出します。

また、関数pの戻り値の型を_ptrdiff_t_に変更することをお勧めします。

86
haccks

aは、nintの(変数)配列です。

&aは、nintの(変数)配列へのポインターです。

(&a)[1]は、最後の配列要素のint one intのポインターです。このポインターは、&a[0]の後のnint要素です。

(&a)[2]は、2つの配列の最後の配列要素を過ぎたint one intのポインターです。このポインターは、2 * nの後の&a[0]int要素です。

(&a)[n]は、int配列の最後の配列要素のint one nのポインターです。このポインターは、n * nの後の&a[0]int要素です。 &a[0]またはaを引くだけで、nが得られます。

もちろん、これは(&a)[n]が配列内または最後の配列要素の1つを超えていないため(Cのポインター演算規則で要求されるように)マシン上で動作する場合でも技術的に未定義の動作です。

35
ouah

同じ配列の2つの要素を指す2つのポインターがある場合、その差はこれらのポインター間の要素の数になります。たとえば、このコードスニペットは2を出力します。

_int a[10];

int *p1 = &a[1];
int *p2 = &a[3];

printf( "%d\n", p2 - p1 ); 
_

表現を考えてみましょう

_(&a)[n] - a;
_

この式では、aの型は_int *_であり、その最初の要素を指します。

式_&a_の型はint ( * )[n]で、イメージ化された2次元配列の最初の行を指します。タイプは異なりますが、その値はaの値と一致します。

_( &a )[n]
_

このイメージ化された2次元配列のn番目の要素であり、タイプ_int[n]_を持ちます。つまり、イメージ化された配列のn番目の行です。式_(&a)[n] - a_では、最初の要素のアドレスに変換され、タイプは `int *です。

したがって、_(&a)[n]_とaの間には、n要素のn行があります。したがって、差は_n * n_と等しくなります。

12
Expression     | Value                | Explanation
a              | a                    | point to array of int elements
a[n]           | a + n*sizeof(int)    | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a             | a                    | point to array of (n int elements array)
(&a)[n]        | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int)      | int[n] is a type of n-int-element array

したがって、

  1. (&a)[n]のタイプはint[n]ポインターです
  2. aのタイプはintポインターです

これで、式(&a)[n]-aはポインターの減算を実行します。

  (&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n
4
onlyice