Có giới hạn số lượng vòng lặp 'for' lồng nhau không?


79

Vì mọi thứ đều có giới hạn, tôi đã tự hỏi liệu có giới hạn số forvòng lặp lồng nhau hay không, hoặc miễn là tôi có bộ nhớ, tôi có thể thêm chúng, trình biên dịch Visual Studio có thể tạo một chương trình như vậy không?

Tất nhiên 64 forvòng lặp lồng nhau trở lên sẽ không tiện để gỡ lỗi, nhưng nó có khả thi không?

private void TestForLoop()
{
  for (int a = 0; a < 4; a++)
  {
    for (int b = 0; b < 56; b++)
    {
      for (int c = 0; c < 196; c++)
      {
        //etc....
      }
    }
  }
}

78
Bạn là một lập trình viên máy tính: hãy trả lời câu hỏi của riêng bạn với lập trình máy tính. Viết chương trình tạo, biên dịch và chạy các chương trình với 1, 2, 4, 8, 16, 32, ... các vòng lặp lồng nhau, và bạn sẽ rất nhanh chóng tìm hiểu nếu có giới hạn.
Eric Lippert

38
# 1. OP đã không chỉ ra rằng điều này theo bất kỳ cách nào liên quan đến mã sản xuất, trái ngược với chỉ là một loại câu hỏi giả định, "cái gì-nếu". # 2. Cho dù OP tạo ra bao nhiêu vòng lặp lồng nhau trong khi thử nghiệm, chúng sẽ không bao giờ đạt đến vô cùng; bất kể OP đẩy nó đi bao xa, cách duy nhất sẽ chứng minh được liệu có giới hạn hay không là nếu và khi nào nó thực sự bị phá vỡ.
Panzercrisis

10
"Vì mọi thứ đều có giới hạn", no: sin (x) không có giới hạn cho x -> oo. Ngoài ra: nếu tiền đề của bạn là "mọi thứ đều bị ràng buộc", tại sao bạn lại hỏi "thứ này có bị ràng buộc không"? Câu trả lời nằm trong giả định của bạn, điều này làm cho lập luận trở nên hoàn toàn vô ích và tầm thường.
Bakuriu

5
@EricLippert Nói một cách chính xác, bạn sẽ không bao giờ chắc chắn liệu có giới hạn không cho dù bạn tiếp tục con đường này bao lâu.
Casey

2
"x -> oo" làm tôi khóc một chút :) Sử dụng "x → ∞"
Manu

Câu trả lời:


120

Tôi đang cố gắng đăng bài này, nhưng tôi nghĩ rằng câu trả lời là:

Từ 550 đến 575

với cài đặt mặc định trong Visual Studio 2015

Tôi đã tạo một chương trình nhỏ tạo các forvòng lặp lồng nhau ...

for (int i0=0; i0<10; i0++)
{
    for (int i1=0; i1<10; i1++)
    {
        ...
        ...
        for (int i573=0; i573<10; i573++)
        {
            for (int i574=0; i574<10; i574++)
            {
                Console.WriteLine(i574);
            }
        }
        ...
        ...
    }
}

Đối với 500 vòng lặp lồng nhau, chương trình vẫn có thể được biên dịch. Với 575 vòng lặp, trình biên dịch giải quyết được:

Cảnh báo AD0001 Analyzer 'Microsoft.CodeAnalysis.CSharp.Diagnostics.SimplifyTypeNames.CSharpSimplifyTypeNamesDiagnosticAnalyzer' đã đưa ra một ngoại lệ loại 'System.InsuffnoughExecutionStackException' với thông báo 'Không đủ ngăn xếp để tiếp tục thực thi chương trình một cách an toàn. Điều này có thể xảy ra do có quá nhiều hàm trên ngăn xếp lệnh gọi hoặc hàm trên ngăn xếp sử dụng quá nhiều không gian ngăn xếp. '.

với thông báo trình biên dịch bên dưới

lỗi CS8078: Một biểu thức quá dài hoặc phức tạp để biên dịch

Tất nhiên, đây là một kết quả hoàn toàn mang tính giả thuyết . Nếu vòng lặp trong cùng nhiều hơn a Console.WriteLinethì có thể có ít vòng lặp lồng nhau hơn trước khi kích thước ngăn xếp bị vượt quá. Ngoài ra, đây có thể không phải là một giới hạn kỹ thuật nghiêm ngặt, theo nghĩa là có thể có các cài đặt ẩn để tăng kích thước ngăn xếp tối đa cho "Trình phân tích" được đề cập trong thông báo lỗi hoặc (nếu cần) cho tệp thực thi kết quả. Tuy nhiên, phần câu trả lời này được dành cho những người hiểu biết sâu về C #.


Cập nhật

Để trả lời câu hỏi trong phần bình luận :

Tôi sẽ quan tâm để xem câu trả lời này mở rộng để "chứng minh" thực nghiệm cho dù bạn có thể đặt 575 biến cục bộ trên stack nếu họ không được sử dụng trong cho-vòng, và / hoặc cho dù bạn có thể đặt 575 phi lồng cho-vòng trong một chức năng duy nhất

Đối với cả hai trường hợp, câu trả lời là: Có, có thể. Khi điền phương thức với 575 câu lệnh được tạo tự động

int i0=0;
Console.WriteLine(i0);
int i1=0;
Console.WriteLine(i1);
...
int i574=0;
Console.WriteLine(i574);

nó vẫn có thể được biên dịch. Mọi thứ khác sẽ làm tôi ngạc nhiên. Kích thước ngăn xếp được yêu cầu cho các intbiến chỉ là 2,3 KB. Nhưng tôi tò mò, và để kiểm tra các giới hạn xa hơn, tôi đã tăng con số này lên. Cuối cùng, nó không biên dịch, gây ra lỗi

lỗi CS0204: Chỉ có 65534 địa phương, bao gồm cả những người được tạo bởi trình biên dịch, được phép

đó là một điểm thú vị, nhưng đã được quan sát ở nơi khác: Số lượng biến tối đa trong phương pháp

Tương tự, 575 -loops không lồng nhau for , như trong

for (int i0=0; i0<10; i0++)
{
    Console.WriteLine(i0);
}
for (int i1=0; i1<10; i1++)
{
    Console.WriteLine(i1);
}
...
for (int i574=0; i574<10; i574++)
{
    Console.WriteLine(i574);
}

cũng có thể được biên dịch. Ở đây, tôi cũng đã cố gắng tìm giới hạn và tạo thêm các vòng lặp này. Đặc biệt, tôi không chắc liệu các biến vòng lặp trong trường hợp này có tính cả "local" hay không, bởi vì chúng nằm trong chính chúng { block }. Tuy nhiên, hơn 65534 là không thể. Cuối cùng, tôi đã thêm một bài kiểm tra bao gồm 40000 vòng lặp của mẫu

for (int i39999 = 0; i39999 < 10; i39999++)
{
    int j = 0;
    Console.WriteLine(j + i39999);
}

có chứa một biến bổ sung trong vòng lặp, nhưng những biến này dường như cũng được tính là "địa phương" và không thể biên dịch biến này.


Tóm lại: Giới hạn ~ 550 thực sự là do độ sâu lồng nhau của các vòng lặp. Điều này cũng được chỉ ra bởi thông báo lỗi

lỗi CS8078: Một biểu thức quá dài hoặc phức tạp để biên dịch

Các tài liệu của CS1647 lỗi không may (nhưng dễ hiểu) không xác định "đo" độ phức tạp, nhưng chỉ cung cấp những lời khuyên thực tế

Đã xảy ra lỗi tràn ngăn xếp trong trình biên dịch xử lý mã của bạn. Để giải quyết lỗi này, hãy đơn giản hóa mã của bạn.

Để nhấn mạnh điều này một lần nữa: Đối với trường hợp cụ thể của for-loops lồng nhau sâu sắc , tất cả điều này là khá hàn lâm và giả thuyết . Nhưng việc tìm kiếm trên web cho thông báo lỗi của CS1647 cho thấy một số trường hợp lỗi này xuất hiện đối với mã rất có thể không cố ý phức tạp, nhưng được tạo ra trong các tình huống thực tế.


2
@PatrickHofman Có, gợi ý "với cài đặt mặc định ..." rất quan trọng: Điều này đề cập đến việc tạo một dự án mặc định trong VS2015 một cách ngây thơ. Cài đặt dự án không hiển thị thông số "rõ ràng" để tăng giới hạn. Nhưng bất kể điều đó, tôi tự hỏi liệu không có bất kỳ giới hạn nào được áp đặt bởi CLI / CLR / bất cứ điều gì. Thông số kỹ thuật ECMA-335 có phần "III.1.7.4 Phải cung cấp maxstack" , nhưng tôi quá khó hiểu về C # noob để nói liệu điều này có thực sự liên quan hay thậm chí phân tích ý nghĩa của nó một cách chi tiết ....
Marco 13

10
Đây sẽ không phải là lần đầu tiên Microsoft có giới hạn về forvòng lặp. Microsoft BASIC có giới hạn lồng vào nhau là 8
Đánh dấu

3
@Davislor: Thông báo lỗi đề cập đến kích thước ngăn xếp trong trình biên dịch , cũng là một chương trình và đang xử lý đệ quy cấu trúc vòng lặp lồng nhau. Nó không bị ràng buộc (mạnh mẽ) với số lượng biến cục bộ trong mã đang được biên dịch.
ruakh

2
Chỉ tôi thôi à? Tôi thấy câu trả lời này hoàn toàn vui nhộn! Tôi cũng sẽ nghĩ 42như câu trả lời.
cmroanirgo

11
Có thể cần lưu ý rằng 500 vòng lặp lồng nhau giả sử chỉ có 2 lần lặp trên mỗi vòng lặp và một lần lặp trên mỗi chu kỳ sẽ mất khoảng 10 ^ 33 googol năm (10 ^ 133) để chạy trên máy tính 4 GHz. Để so sánh tuổi của vũ trụ là khoảng 1,37 x 10 ^ 10 năm. Cho rằng các nucleon được dự đoán sẽ phân rã trong khoảng 10 ^ 40 năm, tôi có thể nói thẳng rằng phần cứng hiện tại không được xây dựng để tồn tại lâu như vậy và bạn có thể cần khởi động lại máy tính trong thời gian chờ đợi.
Maciej Piechotka

40

Không có giới hạn cứng trong đặc tả ngôn ngữ C # hoặc CLR. Mã của bạn sẽ lặp lại thay vì đệ quy, điều này có thể dẫn đến tràn ngăn xếp khá nhanh.

Có một số thứ có thể được tính là một ngưỡng, ví dụ như bộ intđếm (nói chung) mà bạn sẽ sử dụng, bộ đếm này sẽ cấp phát một intbộ nhớ trong bộ nhớ cho mỗi vòng lặp (và trước khi bạn cấp phát toàn bộ ngăn xếp của mình bằng các int ...). Lưu ý rằng việc sử dụng biến đó intlà bắt buộc và bạn có thể sử dụng lại cùng một biến.

Như đã chỉ ra bởi Marco , ngưỡng hiện tại nằm trong trình biên dịch hơn là trong đặc tả ngôn ngữ thực tế hoặc thời gian chạy. Khi điều đó được giải mã, bạn có thể thực hiện thêm một số lần lặp lại. Ví dụ: nếu bạn sử dụng Ideone , theo mặc định sử dụng trình biên dịch cũ, bạn có thể fordễ dàng nhận được hơn 1200 vòng lặp.

Mặc dù vậy, nhiều vòng lặp là một chỉ báo của thiết kế xấu. Tôi hy vọng câu hỏi này hoàn toàn là giả thuyết.


Tôi đồng ý với điều này, nhưng sẽ nói thêm rằng bạn có thể đạt đến giới hạn thời gian / phần cứng trước khi đạt đến giới hạn phần mềm (ví dụ: mất quá nhiều thời gian để biên dịch hoặc mã của bạn mất quá nhiều thời gian / quá nhiều tài nguyên để thực thi).
Marshall Tigerus

Các tệp mã hàng triệu dòng biên dịch khá nhanh, vì vậy đó không phải là vấn đề. Ngoài ra, vì mã này khá thẳng với công cụ thực thi, tôi sẽ không mong đợi quá nhiều từ nó về hiệu suất.
Patrick Hofman

1
Cố lên, không có giới hạn trong chính ngôn ngữ. Đó là hệ thống tập tin chỉ là 20MB và tập tin nguồn hoặc thực thi là nhận được quá lớn là không phải là một giới hạn thực @ Marco13
Patrick Hofman

3
Trái ngược với câu trả lời của bạn, mỗi vòng lặp trên thực tế thêm vào không gian ngăn xếp được sử dụng. (Nó đang thêm một biến mới.) Do đó, ngăn xếp sẽ hết khi bạn có đủ biến. Nó chính xác là vấn đề giống như với đệ quy (ngoại trừ việc khung ngăn xếp cho một phương thức sẽ chiếm nhiều không gian ngăn xếp hơn chỉ một int). Nếu chúng là các vòng lặp không có biến vòng lặp, thì điều đó sẽ khác.
Servy

3
Tôi tin rằng CPython giới hạn lồng các cấu trúc ngôn ngữ vào khoảng 50. Không có ích gì khi hỗ trợ thậm chí 1200 vòng lặp lồng nhau ... 10 là một dấu hiệu rõ ràng cho thấy chương trình có vấn đề.
Bakuriu

13

Có một giới hạn cho tất cả C # được biên dịch thành MSIL. MSIL chỉ có thể hỗ trợ 65535 biến cục bộ. Nếu các forvòng lặp của bạn giống như các vòng lặp bạn đã hiển thị trong ví dụ, mỗi vòng lặp yêu cầu một biến.

Có thể trình biên dịch của bạn có thể phân bổ các đối tượng trên heap để hoạt động như bộ lưu trữ cho các biến cục bộ, vượt qua giới hạn này. Tuy nhiên, tôi không chắc những kết quả kỳ quặc nào sẽ đến từ đó. Có thể có những vấn đề nảy sinh với sự phản ánh khiến cách tiếp cận như vậy trở nên bất hợp pháp.


6
Bạn không cần bất kỳ biến nào cho vòng lặp for.
Patrick Hofman

1
@PatrickHofman Bạn nói đúng, tôi chỉnh sửa trong phần mà tôi quên bao gồm
Cort Ammon

@PatrickHofman nếu tôi không nhầm thì vòng lặp for không có biến tương đương với while(true)vòng lặp sẽ tạo thành vòng lặp vô hạn. Nếu chúng tôi đã chỉnh sửa câu hỏi thành "Có giới hạn số vòng lặp for lồng nhau trong một chương trình kết thúc không?" sau đó chúng ta có thể sử dụng thủ thuật biến cục bộ để tạo giới hạn cứng không?
emory

2
@emory Không có gì ngăn bạn khỏi một số vòng lặp thực sự vô lý sử dụng lại các biến. Tôi sẽ không gọi chúng là vòng lặp có ý nghĩa, nhưng chúng có thể được thực hiện.
Cort Ammon

1
@emory bạn có thể chấm dứt một vòng lặp với một breakhoặc có một cái gì đó vòng lặp kiểm tra điều kiện bên ngoài, giống như( ; DateTime.now() < ... ; )
Grisha Levit

7

Giữa 800 và 900 cho for(;;)các vòng lặp trống .

Bắt chước cách tiếp cận của Marco13, ngoại trừ for(;;)các vòng lặp đã thử :

for (;;)  // 0
for (;;)  // 1
for (;;)  // 2
// ...
for (;;)  // n_max
{
  // empty body
}

Nó hoạt động cho 800 lồng nhau for(;;), nhưng nó lại đưa ra lỗi tương tự mà Marco13 gặp phải khi thử 900 vòng.

Khi nó biên dịch, nó for(;;)xuất hiện để chặn luồng mà không làm tối đa CPU; bề ngoài, nó dường như hoạt động như một Thread.Sleep().


2
"nó kết thúc chạy khá nhiều ngay lập tức" - thật kỳ lạ - Tôi đã nghĩ rằng đó for(;;)sẽ là một vòng lặp vô hạn trong C # ?! (Tôi không thể thử nó ra ngay bây giờ, nhưng nếu nó hóa ra là sai, tôi sẽ xóa bình luận này)
Marco13

@ Marco13: Đúng, bạn nói đúng! Không chắc chắn những gì đã xảy ra trong mã thử nghiệm của tôi trước đó, nhưng nó for(;;)đang chặn mà không tốn thời gian của CPU.
Nat

Vòng lặp for (;;) là một vòng lặp vô hạn, vậy tại sao phải thêm một vòng lặp for (;;) khác hoặc 800 vòng lặp khác vì chúng sẽ không được chạy. cái đầu tiên sẽ chạy mãi mãi.
Fred Smith

1
@FredSmith: Các for(;;)vòng lặp được lồng vào nhau, vì vậy tất cả chúng đều bắt đầu từ vòng lặp lồng nhau nhất, là vô hạn. Đối với người đầu tiên for(;;)chặn, nó phải được for(;;){}. Về lý do tại sao làm điều này, nó chỉ là một thử nghiệm để xem việc triển khai .NET / C # hiện tại có những hạn chế nào; rõ ràng nó không thể đi đến 900 for(;;), mặc dù, như bạn lưu ý, nó không làm gì cả.
Nat
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.