Tại sao điều này cho vòng lặp thoát trên một số nền tảng mà không phải trên các nền tảng khác?


240

Gần đây tôi đã bắt đầu học C và tôi đang tham gia một lớp học với C là môn học. Tôi hiện đang chơi xung quanh với các vòng lặp và tôi đang gặp phải một số hành vi kỳ lạ mà tôi không biết giải thích thế nào.

#include <stdio.h>

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%d \n", sizeof(array)/sizeof(int));
  return 0;
}

Trên máy tính xách tay của tôi chạy Ubuntu 14.04, mã này không bị hỏng. Nó chạy đến hoàn thành. Trên máy tính của trường tôi chạy CentOS 6.6, nó cũng chạy tốt. Trên Windows 8.1, vòng lặp không bao giờ chấm dứt.

Điều kỳ lạ hơn nữa là khi tôi chỉnh sửa điều kiện của forvòng lặp thành : i <= 11, mã chỉ chấm dứt trên máy tính xách tay chạy Ubuntu của tôi. Nó không bao giờ chấm dứt trong CentOS và Windows.

Bất cứ ai cũng có thể giải thích những gì xảy ra trong bộ nhớ và tại sao các hệ điều hành khác nhau chạy cùng một mã cho kết quả khác nhau?

EDIT: Tôi biết vòng lặp for vượt ra ngoài giới hạn. Tôi đang cố tình làm điều đó. Tôi chỉ không thể hiểu làm thế nào hành vi có thể khác nhau trên các hệ điều hành và máy tính khác nhau.


147
Vì bạn đang ghi đè lên mảng nên hành vi không xác định xảy ra. Hành vi không xác định có nghĩa là bất cứ điều gì có thể xảy ra bao gồm cả nó dường như làm việc. Do đó "mã không bao giờ nên chấm dứt" không phải là một kỳ vọng hợp lệ.
kaylum

37
Chính xác, chào mừng bạn đến C. Mảng của bạn có 10 phần tử - được đánh số từ 0 đến 9.
Yetti99

14
@JonCav Bạn đã phá mã. Bạn đang nhận được hành vi không xác định đó là mã bị hỏng.
kaylum

50
Vâng, toàn bộ vấn đề là hành vi không xác định là chính xác đó. Bạn không thể kiểm tra nó một cách đáng tin cậy và chứng minh điều gì đó được xác định sẽ xảy ra. Điều có thể xảy ra trong máy Windows của bạn, đó là biến iđược lưu trữ ngay sau khi kết thúc arrayvà bạn đang ghi đè lên nó array[10]=0;. Đây có thể không phải là trường hợp trong một bản dựng được tối ưu hóa trên cùng một nền tảng, có thể lưu trữ itrong một thanh ghi và không bao giờ đề cập đến nó trong bộ nhớ.
thóc

46
Bởi vì không dự đoán được là một thuộc tính cơ bản của Hành vi không xác định. Bạn cần phải hiểu điều này ... Tuyệt đối tất cả các cược đã tắt.
thóc

Câu trả lời:


356

Trên máy tính xách tay của tôi chạy Ubuntu 14.04, mã này không bị hỏng khi chạy đến khi hoàn thành. Trên máy tính của trường tôi chạy CentOS 6.6, nó cũng chạy tốt. Trên Windows 8.1, vòng lặp không bao giờ chấm dứt.

Điều kỳ lạ hơn là khi tôi chỉnh sửa điều kiện của forvòng lặp thành : i <= 11, mã chỉ chấm dứt trên máy tính xách tay chạy Ubuntu của tôi. CentOS và Windows không bao giờ chấm dứt.

Bạn vừa phát hiện ra bộ nhớ dậm chân. Bạn có thể đọc thêm về nó ở đây: Bộ nhớ của bạn là gì?

Khi bạn phân bổ int array[10],i;, các biến đó sẽ đi vào bộ nhớ (cụ thể, chúng được phân bổ trên ngăn xếp, là một khối bộ nhớ được liên kết với hàm). array[]icó lẽ là liền kề nhau trong bộ nhớ. Có vẻ như trên Windows 8.1, iđược đặt tại array[10]. Trên CentOS, iđược đặt tại array[11]. Và trên Ubuntu, nó không ở đâu cả (có lẽ là tại array[-1]?).

Hãy thử thêm các câu lệnh gỡ lỗi này vào mã của bạn. Bạn nên chú ý rằng trên lần lặp 10 hoặc 11, array[i]điểm tại i.

#include <stdio.h>
 
int main() 
{ 
  int array[10],i; 
 
  printf ("array: %p, &i: %p\n", array, &i); 
  printf ("i is offset %d from array\n", &i - array);

  for (i = 0; i <=11 ; i++) 
  { 
    printf ("%d: Writing 0 to address %p\n", i, &array[i]); 
    array[i]=0; /*code should never terminate*/ 
  } 
  return 0; 
} 

6
Này cảm ơn nhé! Điều đó thực sự giải thích khá nhiều. Trong Windows, nó nói rằng tôi nếu bù 10 từ mảng, trong khi ở cả CentOS và Ubuntu, nó là -1. Điều kỳ lạ là nếu tôi nhận xét mã trình gỡ lỗi của bạn, CentOS không thể chạy mã (bị treo), nhưng với mã gỡ lỗi của bạn, nó sẽ chạy. C dường như là một ngôn ngữ cho đến nay X_x
JonCav

12
@JonCav "nó bị treo" có thể xảy ra nếu viết để array[10]phá hủy khung stack, chẳng hạn. Làm thế nào có thể có sự khác biệt giữa mã có hoặc không có đầu ra gỡ lỗi? Nếu địa chỉ của ikhông bao giờ cần thiết, trình biên dịch có thể tối ưu hóa iđi. vào một thanh ghi, do đó thay đổi cách bố trí bộ nhớ trên ngăn xếp ...
Hagen von Eitzen

2
Tôi không nghĩ nó bị treo, tôi nghĩ rằng nó nằm trong một vòng lặp vô hạn bởi vì nó tải lại bộ đếm vòng lặp từ bộ nhớ (điều này đã bị xóa bởi array[10]=0. Nếu bạn biên dịch mã với tối ưu hóa, điều này có thể sẽ không xảy ra. (Vì C có Các quy tắc bí danh giới hạn các loại truy cập bộ nhớ phải được giả sử có khả năng chồng lấp bộ nhớ khác. Là một biến cục bộ mà bạn không bao giờ lấy địa chỉ, tôi nghĩ rằng một trình biên dịch sẽ có thể giả định rằng không có gì bí danh. của một mảng là hành vi không xác định. Luôn cố gắng hết sức để tránh tùy thuộc vào điều đó
Peter Cordes

4
Một cách khác là trình biên dịch tối ưu hóa loại bỏ hoàn toàn mảng, vì nó không có tác dụng quan sát được (trong mã gốc của câu hỏi). Do đó, mã kết quả chỉ có thể in ra chuỗi không đổi mười một lần, tiếp theo là in kích thước không đổi và do đó làm cho tràn hoàn toàn không được chú ý.
Holger

9
@JonCav Nói chung, tôi sẽ nói rằng bạn không cần biết thêm về quản lý bộ nhớ và thay vào đó chỉ đơn giản là biết không viết mã không xác định, cụ thể, đừng viết quá cuối một mảng ...
T. Kiley

98

Lỗi nằm giữa các đoạn mã này:

int array[10],i;

for (i = 0; i <=10 ; i++)

array[i]=0;

arraychỉ có 10 phần tử, trong lần lặp cuối cùng array[10] = 0;là tràn bộ đệm. Bộ đệm tràn ra được HIỂU R BE RÀNG , có nghĩa là chúng có thể định dạng ổ cứng của bạn hoặc khiến quỷ bay ra khỏi mũi của bạn.

Nó là khá phổ biến cho tất cả các biến ngăn xếp được đặt liền kề nhau. Nếu inằm ở đâu array[10]viết đến, thì UB sẽ thiết lập lại iđể 0, dẫn đến vòng chưa hoàn tất.

Để khắc phục, thay đổi điều kiện vòng lặp thành i < 10.


6
Nitpick: Bạn thực sự không thể định dạng ổ cứng trên bất kỳ HĐH lành mạnh nào trên thị trường trừ khi bạn đang chạy bằng root (hoặc tương đương).
Kevin

26
@Kevin khi bạn gọi UB, bạn từ bỏ mọi yêu cầu về sự tỉnh táo.
o11c

7
Nó không quan trọng cho dù mã của bạn là lành mạnh. HĐH sẽ không cho phép bạn làm điều đó.
Kevin

2
@Kevin Ví dụ với định dạng ổ cứng của bạn bắt nguồn từ lâu trước đó là trường hợp. Ngay cả các bản hòa âm của thời gian (nơi C bắt nguồn) cũng khá vui khi cho phép bạn làm những việc như vậy - và thậm chí ngày nay, rất nhiều bản phân phối sẽ vui vẻ cho phép bạn bắt đầu xóa mọi thứ rm -rf /ngay cả khi bạn không root, không "Định dạng" toàn bộ ổ đĩa, nhưng vẫn phá hủy tất cả dữ liệu của bạn. Ôi.
Luaan

5
@Kevin nhưng hành vi không xác định có thể khai thác lỗ hổng hệ điều hành và sau đó tự nâng cao để cài đặt trình điều khiển đĩa cứng mới và sau đó bắt đầu chà ổ đĩa.
quái vật ratchet

38

Trong lần chạy cuối cùng của vòng lặp, bạn viết vào array[10], nhưng chỉ có 10 phần tử trong mảng, được đánh số từ 0 đến 9. Đặc tả ngôn ngữ C nói rằng đây là hành vi không xác định được. Điều này có nghĩa trong thực tế là chương trình của bạn sẽ cố ghi vào intphần bộ nhớ có kích thước nằm ngay sau arraybộ nhớ. Điều gì xảy ra sau đó phụ thuộc vào những gì thực tế nằm ở đó và điều này không chỉ phụ thuộc vào hệ điều hành mà còn phụ thuộc vào trình biên dịch, vào các tùy chọn trình biên dịch (như cài đặt tối ưu hóa), vào kiến ​​trúc bộ xử lý, vào mã xung quanh , v.v ... Nó thậm chí có thể thay đổi từ thực thi sang thực thi, ví dụ do ngẫu nhiên không gian địa chỉ (có thể không có trong ví dụ về đồ chơi này, nhưng nó xảy ra trong cuộc sống thực). Một số khả năng bao gồm:

  • Vị trí không được sử dụng. Vòng lặp chấm dứt bình thường.
  • Vị trí được sử dụng cho một cái gì đó có giá trị 0. Vòng lặp kết thúc bình thường.
  • Vị trí chứa địa chỉ trả về của hàm. Vòng lặp kết thúc bình thường, nhưng sau đó chương trình gặp sự cố vì nó cố nhảy đến địa chỉ 0.
  • Vị trí chứa biến i. Vòng lặp không bao giờ kết thúc vì ikhởi động lại ở 0.
  • Vị trí chứa một số biến khác. Vòng lặp chấm dứt bình thường, nhưng sau đó, những điều thú vị trên mạng xảy ra.
  • Vị trí là một địa chỉ bộ nhớ không hợp lệ, ví dụ: vì arrayở ngay cuối trang bộ nhớ ảo và trang tiếp theo không được ánh xạ.
  • Quỷ bay ra khỏi mũi của bạn . May mắn là hầu hết các máy tính thiếu phần cứng cần thiết.

Những gì bạn quan sát thấy trên Windows là trình biên dịch đã quyết định đặt biến ingay sau mảng trong bộ nhớ, vì vậy array[10] = 0cuối cùng đã gán cho i. Trên Ubuntu và CentOS, trình biên dịch không được đặt iở đó. Hầu như tất cả các cài đặt C thực hiện nhóm biến cục bộ trong bộ nhớ, trên ngăn xếp bộ nhớ , với một ngoại lệ chính: một số biến cục bộ có thể được đặt hoàn toàn trong các thanh ghi . Ngay cả khi biến nằm trên ngăn xếp, thứ tự các biến được xác định bởi trình biên dịch và nó có thể không chỉ phụ thuộc vào thứ tự trong tệp nguồn mà còn phụ thuộc vào các loại của chúng (để tránh lãng phí bộ nhớ cho các ràng buộc căn chỉnh sẽ để lại lỗ hổng) , trên tên của họ, trên một số giá trị băm được sử dụng trong cấu trúc dữ liệu nội bộ của trình biên dịch, v.v.

Nếu bạn muốn tìm hiểu xem trình biên dịch của bạn đã quyết định làm gì, bạn có thể yêu cầu nó hiển thị cho bạn mã trình biên dịch. Ồ, và học cách giải mã trình biên dịch chương trình (nó dễ hơn viết nó). Với GCC (và một số trình biên dịch khác, đặc biệt là trong thế giới Unix), hãy vượt qua tùy chọn -Sđể tạo mã trình biên dịch thay vì nhị phân. Ví dụ: đây là đoạn mã trình biên dịch cho vòng lặp từ biên dịch với GCC trên amd64 với tùy chọn tối ưu hóa -O0(không tối ưu hóa), với các nhận xét được thêm thủ công:

.L3:
    movl    -52(%rbp), %eax           ; load i to register eax
    cltq
    movl    $0, -48(%rbp,%rax,4)      ; set array[i] to 0
    movl    $.LC0, %edi
    call    puts                      ; printf of a constant string was optimized to puts
    addl    $1, -52(%rbp)             ; add 1 to i
.L2:
    cmpl    $10, -52(%rbp)            ; compare i to 10
    jle     .L3

Ở đây biến ilà 52 byte bên dưới đỉnh của ngăn xếp, trong khi mảng bắt đầu 48 byte bên dưới đỉnh của ngăn xếp. Vì vậy, trình biên dịch này đã xảy ra ingay trước mảng; bạn sẽ ghi đè lên inếu bạn tình cờ viết thư cho array[-1]. Nếu bạn thay đổi array[i]=0thành array[9-i]=0, bạn sẽ nhận được một vòng lặp vô hạn trên nền tảng cụ thể này với các tùy chọn trình biên dịch cụ thể này.

Bây giờ hãy biên dịch chương trình của bạn với gcc -O1.

    movl    $11, %ebx
.L3:
    movl    $.LC0, %edi
    call    puts
    subl    $1, %ebx
    jne     .L3

Nó ngắn hơn! Trình biên dịch không chỉ từ chối phân bổ vị trí ngăn xếp cho i- nó chỉ được lưu trong sổ đăng ký ebx- nhưng nó không bận tâm phân bổ bất kỳ bộ nhớ nào cho arrayhoặc tạo mã để đặt các phần tử của nó, bởi vì nó nhận thấy rằng không có phần tử nào được sử dụng

Để làm cho ví dụ này trở nên thú vị hơn, hãy đảm bảo rằng các phép gán mảng được thực hiện bằng cách cung cấp cho trình biên dịch một cái gì đó mà nó không thể tối ưu hóa. Một cách dễ dàng để làm điều đó là sử dụng mảng từ một tệp khác - do quá trình biên dịch riêng biệt, trình biên dịch không biết điều gì xảy ra trong một tệp khác (trừ khi nó tối ưu hóa tại thời điểm liên kết, điều này gcc -O0hay gcc -O1không). Tạo một tệp nguồn use_array.cchứa

void use_array(int *array) {}

và thay đổi mã nguồn của bạn thành

#include <stdio.h>
void use_array(int *array);

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%zd \n", sizeof(array)/sizeof(int));
  use_array(array);
  return 0;
}

Biên dịch với

gcc -c use_array.c
gcc -O1 -S -o with_use_array1.c with_use_array.c use_array.o

Lần này mã trình biên dịch mã trông như thế này:

    movq    %rsp, %rbx
    leaq    44(%rsp), %rbp
.L3:
    movl    $0, (%rbx)
    movl    $.LC0, %edi
    call    puts
    addq    $4, %rbx
    cmpq    %rbp, %rbx
    jne     .L3

Bây giờ mảng nằm trên ngăn xếp, 44 byte từ đầu. Thế còn i? Nó không xuất hiện ở bất cứ đâu! Nhưng bộ đếm vòng lặp được giữ trong thanh ghi rbx. Nó không chính xác i, nhưng địa chỉ của array[i]. Trình biên dịch đã quyết định rằng vì giá trị của ikhông bao giờ được sử dụng trực tiếp, nên không có điểm nào trong việc thực hiện số học để tính toán nơi lưu trữ 0 trong mỗi lần chạy vòng lặp. Thay vào đó, địa chỉ đó là biến vòng lặp và số học để xác định các ranh giới được thực hiện một phần tại thời gian biên dịch (nhân 11 lần lặp với 4 byte cho mỗi phần tử mảng để có 44) và một phần trong thời gian chạy nhưng một lần và mãi mãi trước khi vòng lặp bắt đầu ( thực hiện phép trừ để lấy giá trị ban đầu).

Ngay cả trong ví dụ rất đơn giản này, chúng ta đã thấy cách thay đổi tùy chọn trình biên dịch (bật tối ưu hóa) hoặc thay đổi thứ gì đó nhỏ ( array[i]thành array[9-i]) hoặc thậm chí thay đổi thứ gì đó dường như không liên quan (thêm lệnh gọi use_array) có thể tạo ra sự khác biệt đáng kể với những gì chương trình thực thi được tạo bởi trình biên dịch nào. Tối ưu hóa trình biên dịch có thể làm rất nhiều thứ có vẻ không trực quan trên các chương trình gọi hành vi không xác định . Đó là lý do tại sao hành vi không xác định được hoàn toàn không xác định. Khi bạn đi chệch một chút so với các bài hát, trong các chương trình trong thế giới thực, có thể rất khó hiểu mối quan hệ giữa những gì mã làm và những gì nó nên làm, ngay cả đối với các lập trình viên có kinh nghiệm.


25

Không giống như Java, C không thực hiện kiểm tra ranh giới mảng, tức là không có ArrayIndexOutOfBoundsException, công việc đảm bảo chỉ số mảng hợp lệ được để lại cho lập trình viên. Làm điều này trên mục đích dẫn đến hành vi không xác định, bất cứ điều gì có thể xảy ra.


Đối với một mảng:

int array[10]

chỉ mục chỉ có giá trị trong phạm vi 0đến 9. Tuy nhiên, bạn đang cố gắng:

for (i = 0; i <=10 ; i++)

truy cập vào array[10]đây, thay đổi điều kiện đểi < 10


6
Làm điều đó không nhằm mục đích cũng dẫn đến hành vi không xác định - trình biên dịch không thể nói! ;-)
Toby Speight

1
Chỉ cần sử dụng macro để đưa ra lỗi của bạn dưới dạng cảnh báo: #define UNINTENDED_MISTAKE (EXP) printf ("Cảnh báo:" #EXP "nhầm \ n");
lkraider

1
Ý tôi là, nếu bạn đang làm sai mục đích, bạn cũng có thể xác định nó như vậy và làm cho nó an toàn để tránh hành vi không xác định; D
lkraider

19

Bạn có một vi phạm giới hạn và trên các nền tảng không kết thúc, tôi tin rằng bạn đang vô tình đặt ivề 0 ở cuối vòng lặp, để nó bắt đầu lại từ đầu.

array[10]không có hiệu lực; Nó chứa 10 yếu tố, array[0]thông qua array[9]array[10]là thứ 11. Vòng lặp của bạn nên được viết để dừng trước 10 , như sau:

for (i = 0; i < 10; i++)

Trường hợp các array[10]vùng đất được xác định theo triển khai và thật thú vị, trên hai trong số các nền tảng của bạn, nó sẽ nằm trên iđó, những nền tảng đó dường như nằm ngay sau đó array. iđược đặt thành không và vòng lặp tiếp tục mãi mãi. Đối với các nền tảng khác của bạn, icó thể được đặt trước arrayhoặc arraycó thể có một số phần đệm sau nó.


Tôi không nghĩ valgrind có thể nắm bắt được điều này vì đây vẫn là một địa điểm hợp lệ, nhưng ASAN thì có thể.
o11c

13

Bạn khai báo int array[10]phương tiện arraycó chỉ số 0để 9(tổng 10các yếu tố nguyên nó có thể giữ). Nhưng vòng lặp sau,

for (i = 0; i <=10 ; i++)

sẽ lặp 0để 10có nghĩa là 11thời gian. Do đó, khi i = 10nó sẽ tràn bộ đệm và gây ra Hành vi không xác định .

Vì vậy, hãy thử điều này:

for (i = 0; i < 10 ; i++)

hoặc là,

for (i = 0; i <= 9 ; i++)

7

Nó không được xác định tại array[10]và đưa ra hành vi không xác định như được mô tả trước đây. Hãy suy nghĩ về nó như thế này:

Tôi có 10 mặt hàng trong giỏ hàng tạp hóa của tôi. Họ đang:

0: Một hộp ngũ cốc
1: Bánh mì
2: Sữa
3: Bánh
4: Trứng
5: Bánh
6: Một lít soda
7: Salad
8: Bánh mì kẹp thịt
9: Kem

cart[10]không được xác định và có thể đưa ra một ngoại lệ giới hạn trong một số trình biên dịch. Nhưng, rất nhiều dường như không. Mục thứ 11 rõ ràng là một mục không thực sự trong giỏ hàng. Mục thứ 11 đang chỉ vào, thứ mà tôi sẽ gọi, một "mục poltergeist". Nó không bao giờ tồn tại, nhưng nó đã ở đó.

Tại sao một số trình biên dịch cho imột chỉ số của array[10]hoặc array[11]hoặc thậm chí array[-1]là vì tuyên bố khởi / khai của bạn. Một số trình biên dịch giải thích điều này là:

  • "Phân bổ 10 khối ints cho array[10]và một intkhối khác . Để dễ dàng hơn, hãy đặt chúng ngay cạnh nhau."
  • Tương tự như trước đây, nhưng di chuyển nó một hoặc hai khoảng cách, do đó array[10]không trỏ đến i.
  • Làm tương tự như trước, nhưng phân bổ i tại array[-1](vì chỉ mục của một mảng không thể hoặc không nên âm) hoặc phân bổ nó ở một vị trí hoàn toàn khác vì HĐH có thể xử lý nó và an toàn hơn.

Một số trình biên dịch muốn mọi thứ diễn ra nhanh hơn và một số trình biên dịch thích sự an toàn. Đó là tất cả về bối cảnh. Ví dụ, nếu tôi đang phát triển một ứng dụng cho HĐH BREW cổ đại (HĐH của điện thoại cơ bản), thì nó sẽ không quan tâm đến sự an toàn. Nếu tôi đang phát triển cho iPhone 6, thì nó có thể chạy nhanh bất kể là gì, vì vậy tôi sẽ cần nhấn mạnh vào sự an toàn. (Nghiêm túc, bạn đã đọc Nguyên tắc App Store của Apple hay đọc về sự phát triển của Swift và Swift 2.0?)


Lưu ý: Tôi đã nhập danh sách để nó đi "0, 1, 2, 3, 4, 5, 6, 7, 8, 9", nhưng ngôn ngữ Đánh dấu của SO đã cố định các vị trí trong danh sách được sắp xếp của tôi.
DDPWNAGE

6

Vì bạn đã tạo một mảng có kích thước 10, nên điều kiện vòng lặp sẽ như sau:

int array[10],i;

for (i = 0; i <10 ; i++)
{

Hiện tại bạn đang cố gắng truy cập vị trí chưa được gán từ bộ nhớ bằng cách sử dụng array[10]và nó đang gây ra hành vi không xác định . Hành vi không xác định có nghĩa là chương trình của bạn sẽ hành xử không xác định thời trang, do đó, nó có thể cung cấp các đầu ra khác nhau trong mỗi lần thực hiện.


5

Theo truyền thống, trình biên dịch C không kiểm tra giới hạn. Bạn có thể gặp lỗi phân đoạn trong trường hợp bạn đề cập đến một vị trí không "thuộc về" quy trình của bạn. Tuy nhiên, các biến cục bộ được phân bổ trên ngăn xếp và tùy thuộc vào cách phân bổ bộ nhớ, khu vực nằm ngoài mảng ( array[10]) có thể thuộc về phân đoạn bộ nhớ của quy trình. Do đó, không có bẫy lỗi phân đoạn được ném và đó là những gì bạn dường như trải nghiệm. Như những người khác đã chỉ ra, đây là hành vi không xác định trong C và mã của bạn có thể được coi là thất thường. Vì bạn đang học C, tốt hơn hết bạn nên tập thói quen kiểm tra các giới hạn trong mã của mình.


4

Ngoài khả năng bộ nhớ có thể được đặt ra để cố gắng ghi vào a[10]ghi đè thực sự i, cũng có khả năng trình biên dịch tối ưu hóa có thể xác định rằng kiểm tra vòng lặp có thể đạt được với giá trị ilớn hơn mười mà không cần mã truy cập lần đầu phần tử mảng không tồn tạia[10] .

Vì một nỗ lực truy cập phần tử đó sẽ là hành vi không xác định, trình biên dịch sẽ không có nghĩa vụ liên quan đến những gì chương trình có thể làm sau thời điểm đó. Cụ thể hơn, vì trình biên dịch sẽ không có nghĩa vụ tạo mã để kiểm tra chỉ số vòng lặp trong mọi trường hợp có thể lớn hơn mười, nên sẽ không có nghĩa vụ phải tạo mã để kiểm tra mã; thay vào đó, nó có thể cho rằng <=10thử nghiệm sẽ luôn mang lại kết quả đúng. Lưu ý rằng điều này sẽ đúng ngay cả khi mã sẽ đọc a[10]thay vì viết nó.


3

Khi bạn lặp lại quá khứ, i==9bạn gán số 0 cho 'các mục mảng' thực sự nằm ở phía trước mảng , vì vậy bạn sẽ ghi đè lên một số dữ liệu khác. Hầu hết có lẽ bạn ghi đè lên ibiến, được đặt sau a[]. Bằng cách đó, bạn chỉ cần đặt lại ibiến về không và do đó khởi động lại vòng lặp.

Bạn có thể tự khám phá điều đó nếu bạn in itrong vòng lặp:

      printf("test i=%d\n", i);

Thay vì chỉ

      printf("test \n");

Tất nhiên, kết quả đó phụ thuộc rất nhiều vào việc cấp phát bộ nhớ cho các biến của bạn, do đó phụ thuộc vào trình biên dịch và cài đặt của nó, do đó, Hành vi thường không xác định - đó là lý do tại sao kết quả trên các máy khác nhau hoặc hệ điều hành khác nhau hoặc trên các trình biên dịch khác nhau có thể khác nhau.


0

lỗi nằm ở mảng phần [10] w / c cũng là địa chỉ của i (int mảng [10], i;). khi mảng [10] được đặt thành 0 thì i sẽ là 0 w / c đặt lại toàn bộ vòng lặp và gây ra vòng lặp vô hạn. sẽ có vòng lặp vô hạn nếu mảng [10] nằm trong khoảng 0-10. Vòng lặp đúng phải dành cho (i = 0; i <10; i ++) {...} int mảng [10], i; cho mảng (i = 0; i <= 10; i ++) [i] = 0;


0

Tôi sẽ đề xuất một cái gì đó mà tôi không tìm thấy ở trên:

Hãy thử gán mảng [i] = 20;

Tôi đoán điều này sẽ chấm dứt mã ở mọi nơi .. (với điều kiện bạn giữ i <= 10 hoặc ll)

Nếu điều này chạy, bạn có thể quyết định chắc chắn rằng các câu trả lời được chỉ định ở đây đã chính xác [câu trả lời liên quan đến bộ nhớ dậm chân cho ví dụ.]


-9

Có hai điều sai ở đây. Int i thực sự là một phần tử mảng, mảng [10], như đã thấy trên ngăn xếp. Bởi vì bạn đã cho phép lập chỉ mục thực sự tạo mảng [10] = 0, chỉ số vòng lặp, i, sẽ không bao giờ vượt quá 10. Tạo nó for(i=0; i<10; i+=1).

i ++ là, như K & R sẽ gọi nó là "phong cách xấu". Nó tăng i theo kích thước của i, không phải 1. i ++ dành cho toán con trỏ và i + = 1 dành cho đại số. Trong khi điều này phụ thuộc vào trình biên dịch, nó không phải là một quy ước tốt cho tính di động.


5
-1 Hoàn toàn sai. Biến ilà phần tử mảng KHÔNG a[10], không có nghĩa vụ hoặc thậm chí đề xuất cho trình biên dịch đặt nó vào ngăn xếp ngay sau a[] đó - nó cũng có thể được đặt trước mảng hoặc được phân tách bằng một số khoảng trắng bổ sung. Nó thậm chí có thể được phân bổ bên ngoài bộ nhớ chính, ví dụ như trong một thanh ghi CPU. Nó cũng không đúng ++cho con trỏ và không cho số nguyên. Hoàn toàn sai là 'i ++ đang tăng i theo kích thước của i' - đọc mô tả toán tử trong định nghĩa ngôn ngữ!
CiaPan

đó là lý do tại sao nó hoạt động trên một số nền tảng chứ không phải các nền tảng khác. đó là lời giải thích hợp lý duy nhất cho lý do tại sao nó lặp lại mãi mãi trên windows. đối với I ++, nó là con trỏ toán học không phải là số nguyên. đọc Kinh thánh ... 'ngôn ngữ lập trình C'. bởi Kernigan và Ritche, nếu bạn muốn tôi có một bản sao có chữ ký và đã được lập trình từ c năm 1981.
SkipBerne

1
Đọc mã nguồn của OP và tìm khai báo biến i- nó thuộc intloại. Nó là một số nguyên , không phải là một con trỏ; một số nguyên, được sử dụng như một chỉ mục cho array,.
CiaPan

1
Tôi đã làm và đó là lý do tại sao tôi nhận xét như tôi đã làm. có lẽ bạn nên nhận ra rằng trừ khi trình biên dịch bao gồm kiểm tra ngăn xếp và trong trường hợp này, nó không quan trọng như tham chiếu ngăn xếp khi I = 10 thực sự sẽ được tham chiếu, trong một số biên dịch, chỉ mục mảng và nằm trong giới hạn của vùng ngăn xếp. trình biên dịch không thể sửa chữa ngu ngốc. các trình biên dịch có thể tạo ra một bản sửa lỗi như nó xuất hiện, nhưng một cách giải thích thuần túy của ngôn ngữ lập trình c sẽ không hỗ trợ quy ước này và như OP đã nói dẫn đến kết quả không khả chuyển.
SkipBerne

@SkipBerne: Cân nhắc xóa câu trả lời của bạn trước khi bạn được "trao giải" với nhiều điểm tiêu cực hơn.
Peter VARGA
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.