Không thể hiểu cách này để tính bình phương của một số


135

Tôi đã tìm thấy một hàm tính bình phương của một số:

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

Nó trả về giá trị của n 2 . Câu hỏi là, làm thế nào để làm điều đó? Sau một chút thử nghiệm, tôi thấy rằng giữa (&a)[k](&a)[k+1]sizeof(a)/ sizeof(int). Tại sao vậy?


6
Bạn có liên kết đến nơi bạn tìm thấy thông tin này?
R Sahu

4
int p(n)? Điều đó thậm chí còn biên dịch?
barak manos

78
Điều này thật tuyệt vời, bây giờ không bao giờ sử dụng nó nữa và sử dụng n * n thay vào đó ...

26
hoặc tốt hơn:int q(int n) { return sizeof (char [n][n]); }
ouah

17
@ouah giả sử câu hỏi này đề cập đến codegolf.stackexchange.com/a/43262/967 lý do tôi không sử dụng sizeoflà để lưu các ký tự. Mọi người khác: đây là mã cố ý che khuất, đó là hành vi không xác định, câu trả lời của @ ouah là chính xác.
ecatmur

Câu trả lời:


117

Rõ ràng là một hack ... nhưng một cách bình phương một số mà không sử dụng *toán tử (đây là một yêu cầu của cuộc thi mã hóa).

(&a)[n] 

tương đương với một con trỏ tới intvị trí

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

và do đó toàn bộ biểu thức là

  (&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

11
Và như bạn ngụ ý rõ ràng nhưng tôi cảm thấy cần phải nói rõ ràng, đó là một cú pháp tốt nhất. Hoạt động nhân lên vẫn sẽ ở dưới đó; đó chỉ là toán tử đang tránh
Tommy

Tôi đã hiểu những gì nó xảy ra đằng sau, nhưng câu hỏi thực sự của tôi là tại sao (& a) [k] ở cùng địa chỉ với + k * sizeof (a) / sizeof (int)
Emanuel

33
Là một lập trình viên cũ, tôi rất ngạc nhiên bởi thực tế là trình biên dịch có thể coi (&a)như một con trỏ tới một đối tượng n*sizeof(int)khi nkhông biết tại thời điểm biên dịch. C từng là một ngôn ngữ đơn giản ...
Floris

Đó là một cách hack khá thông minh, nhưng một cái gì đó mà bạn sẽ không thấy trong mã sản xuất (hy vọng).
John Odom

14
Bên cạnh đó, nó cũng là UB, vì nó tăng một con trỏ để trỏ đến một phần tử của mảng bên dưới, cũng không chỉ là quá khứ.
Ded

86

Để hiểu về hack này, trước tiên bạn cần hiểu sự khác biệt của con trỏ, nghĩa là, điều gì xảy ra khi hai con trỏ trỏ đến các phần tử của cùng một mảng bị trừ?

Khi một con trỏ bị trừ đi từ một con trỏ khác, kết quả là khoảng cách (được đo bằng các phần tử mảng) giữa các con trỏ. Vì vậy, nếu pđiểm đến a[i]qtrỏ đến a[j], thì p - qbằngi - j .

C11: 6.5.6 Toán tử phụ gia (p9):

Khi hai con trỏ bị trừ , cả hai sẽ trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử qua phần tử cuối cùng của đối tượng mảng; kết quả là sự khác biệt của các chỉ số của hai phần tử mảng . [...].
Nói cách khác, nếu các biểu thức PQtrỏ đến, tương ứng, các phần tử -th ij-th của một đối tượng mảng, biểu thức (P)-(Q)có giá trịi−j được cung cấp giá trị phù hợp với một đối tượng thuộc loại ptrdiff_t.

Bây giờ tôi đang mong đợi rằng bạn nhận thức được việc chuyển đổi tên mảng thành con trỏ, achuyển đổi thành con trỏ thành phần tử đầu tiên của mảng a. &alà địa chỉ của toàn bộ khối bộ nhớ, tức là nó là một địa chỉ của mảng a. Hình dưới đây sẽ giúp bạn hiểu ( đọc câu trả lời này để được giải thích chi tiết ):

nhập mô tả hình ảnh ở đây

Điều này sẽ giúp bạn hiểu rằng tại sao a&acó cùng địa chỉ và (&a)[i]địa chỉ của mảng thứ i (có cùng kích thước với địa chỉ củaa ).

Vì vậy, tuyên bố

return (&a)[n] - a; 

tương đương với

return (&a)[n] - (&a)[0];  

và sự khác biệt này sẽ đưa ra số lượng phần tử giữa các con trỏ (&a)[n](&a)[0], là ncác mảng từng n intphần tử. Do đó, tổng các phần tử mảng là n*n= n2 .


GHI CHÚ:

C11: 6.5.6 Toán tử phụ gia (p9):

Khi hai con trỏ bị trừ, cả hai sẽ trỏ đến các phần tử của cùng một đối tượng mảng hoặc một phần tử qua phần tử cuối cùng của đối tượng mảng ; kết quả là sự khác biệt của các chỉ số của hai phần tử mảng. Kích thước của kết quả được xác định theo thực hiện và loại của nó (một kiểu số nguyên đã ký) được ptrdiff_txác định trong <stddef.h>tiêu đề. Nếu kết quả không thể biểu diễn trong một đối tượng thuộc loại đó, thì hành vi không được xác định.

(&a)[n]không trỏ đến các phần tử của cùng một đối tượng mảng và cũng không đi qua phần tử cuối cùng của đối tượng mảng, (&a)[n] - asẽ gọi hành vi không xác định .

Cũng lưu ý rằng, tốt hơn để thay đổi kiểu trả về của hàm pđể ptrdiff_t.


"cả hai sẽ chỉ đến các phần tử của cùng một đối tượng mảng" - điều này đặt ra câu hỏi cho tôi liệu "hack" này có phải là UB không. Biểu thức số học con trỏ đang đề cập đến kết thúc giả thuyết của một đối tượng không tồn tại: Điều này thậm chí có được phép không?
Martin Ba

Tóm lại, a là địa chỉ của một mảng gồm n phần tử, vì vậy & a [0] là địa chỉ của phần tử đầu tiên trong mảng này, giống với a; ngoài ra, & a [k] sẽ luôn được coi là địa chỉ của một mảng gồm n phần tử, bất kể k và vì & a [1..n] cũng là một vectơ, "vị trí" của các phần tử của nó là liên tiếp, có nghĩa là phần tử đầu tiên ở vị trí x, phần tử thứ hai ở vị trí x + (số phần tử của vectơ a là n), v.v. Tôi có đúng không Ngoài ra, đây là một không gian heap, vì vậy có nghĩa là nếu tôi phân bổ một vectơ mới có cùng n phần tử, địa chỉ của nó có giống như (& a) [1] không?
Emanuel

1
@Emanuel; &a[k]là một địa chỉ của kphần tử thứ của mảng a. Đó (&a)[k]sẽ luôn được coi là một địa chỉ của một loạt các kyếu tố. Vì vậy, phần tử đầu tiên ở vị trí a(hoặc &a), phần tử thứ hai ở vị trí a+ (số phần tử của mảng an) * (kích thước của phần tử mảng), v.v. Và lưu ý rằng, bộ nhớ cho các mảng có chiều dài thay đổi được phân bổ trên stack chứ không phải trên heap.
haccks

@MartinBa; Điều này thậm chí có được phép không? Không. Nó không được phép. UB của nó. Xem chỉnh sửa.
haccks

1
@haccks sự trùng hợp tuyệt vời giữa bản chất của câu hỏi và biệt danh của bạn
Dimitar Tsonev

35

alà một mảng (biến) của n int.

&alà một con trỏ tới một mảng (biến) của n int.

(&a)[1]là một con trỏ của intmột inttrong các phần tử mảng cuối cùng. Con trỏ này là n intcác phần tử sau &a[0].

(&a)[2]là một con trỏ của intmột intquá khứ phần tử mảng cuối cùng của hai mảng. Con trỏ này là 2 * n intcác phần tử sau &a[0].

(&a)[n]là một con trỏ của intmột intphần tử mảng cuối cùng của nmảng. Con trỏ này là n * n intcác phần tử sau &a[0]. Chỉ cần trừ &a[0]hoặc avà bạn có n.

Tất nhiên đây là hành vi không xác định về mặt kỹ thuật ngay cả khi nó hoạt động trên máy của bạn vì (&a)[n]không trỏ vào bên trong mảng hoặc vượt qua phần tử mảng cuối cùng (theo yêu cầu của quy tắc C về số học con trỏ).


Vâng, tôi đã nhận được điều đó, nhưng tại sao điều này xảy ra trong C? Logic đằng sau này là gì?
Emanuel

@Emanuel không có câu trả lời chặt chẽ hơn cho điều đó thực sự hơn số học con trỏ đó rất hữu ích để đo khoảng cách (thường là trong một mảng), [n]cú pháp khai báo một mảng và mảng phân tách thành con trỏ. Ba điều hữu ích riêng biệt với hậu quả này.
Tommy

1
@Emanuel nếu bạn hỏi tại sao ai đó sẽ làm điều này, có rất ít lý do và mọi lý do không phải do tính chất UB của hành động. Và điều đáng chú ý (&a)[n]là loại int[n], và điều đó thể hiện là int*do các mảng biểu thị như địa chỉ của phần tử đầu tiên của chúng, trong trường hợp không rõ ràng trong mô tả.
WhozCraig

Không, tôi không có ý tại sao ai đó làm điều này, ý tôi là tại sao tiêu chuẩn C lại hành xử như vậy trong tình huống này.
Emanuel

1
@Emanuel Con trỏ Số học (và trong trường hợp này là một chương trình con của chủ đề đó: phân biệt con trỏ ). Nó đáng để googling cũng như đọc câu hỏi và câu trả lời trên trang web này. nó có nhiều lợi ích hữu ích và được xác định cụ thể trong các tiêu chuẩn khi được sử dụng đúng cách. Để hoàn toàn nắm bắt được nó, bạn phải hiểu làm thế nào các loại trong mã bạn liệt kê được chiếm dụng.
WhozCraig

12

Nếu bạn có hai con trỏ trỏ đến hai phần tử của cùng một mảng thì sự khác biệt của nó sẽ mang lại số lượng phần tử giữa các con trỏ này. Ví dụ đoạn mã này sẽ xuất ra 2.

int a[10];

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

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

Bây giờ hãy xem xét biểu thức

(&a)[n] - a;

Trong biểu thức anày có loạiint * và trỏ đến phần tử đầu tiên của nó.

Biểu thức &acó loại int ( * )[n]và trỏ đến hàng đầu tiên của mảng hai chiều được tạo ảnh. Giá trị của nó phù hợp với giá trị amặc dù các loại là khác nhau.

( &a )[n]

là phần tử thứ n của mảng hai chiều được tạo hình này và có kiểu int[n] Đó là hàng thứ n của mảng được tạo ảnh. Trong biểu hiện(&a)[n] - a nó được chuyển đổi thành địa chỉ của phần tử đầu tiên và có kiểu `int *.

Vì vậy, giữa (&a)[n]acó n hàng của n phần tử. Vì vậy, sự khác biệt sẽ bằng n * n.


Vậy đằng sau mỗi mảng có một ma trận có kích thước n * n?
Emanuel

@Emanuel Giữa hai con trỏ này có một ma trận các phần tử nxn. Và sự khác biệt của các con trỏ cho giá trị bằng n * n đó là có bao nhiêu phần tử nằm giữa các con trỏ.
Vlad từ Moscow

Nhưng tại sao ma trận này có kích thước n * n phía sau? Nó có sử dụng trong C không? Ý tôi là, nó giống như C "phân bổ" nhiều mảng có kích thước n, mà tôi không biết? Nếu vậy, tôi có thể sử dụng chúng? Mặt khác, tại sao ma trận này sẽ được hình thành (ý tôi là, nó phải có mục đích cho nó ở đó).
Emanuel

2
@Emanuel - Ma trận này chỉ là một lời giải thích về cách số học con trỏ hoạt động trong trường hợp này. Ma trận này không được phân bổ và bạn không thể sử dụng nó. Giống như đã được nêu một vài lần, 1) đoạn mã này là một bản hack không có công dụng thực tế; 2) bạn cần học cách số học con trỏ hoạt động để hiểu được vụ hack này.
void_ptr

@Emanuel Điều này giải thích số học con trỏ. Expreesion (& a) [n] là con trỏ tới n- phần tử của mảng hai chiều được tạo hình do số học con trỏ.
Vlad từ Moscow

4
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

Như vậy

  1. loại (&a)[n]int[n]con trỏ
  2. loại aintcon trỏ

Bây giờ biểu thức (&a)[n]-athực hiện một chuỗi con trỏ:

  (&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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.