Là biến cục bộ chưa được khởi tạo là trình tạo số ngẫu nhiên nhanh nhất?


329

Tôi biết biến cục bộ chưa được khởi tạo là hành vi không xác định ( UB ) và giá trị có thể có các biểu diễn bẫy có thể ảnh hưởng đến hoạt động tiếp theo, nhưng đôi khi tôi chỉ muốn sử dụng số ngẫu nhiên để biểu diễn trực quan và sẽ không sử dụng chúng trong phần khác của ví dụ, chương trình đặt thứ gì đó có màu ngẫu nhiên trong hiệu ứng hình ảnh, ví dụ:

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

nó có nhanh hơn không

void updateEffect(){
    for(int i=0;i<1000;i++){
        star[i].setColor(rand()%255,rand()%255,rand()%255);
        star[i].setVisible(rand()%2==0?true:false);
    }
}

và cũng nhanh hơn so với trình tạo số ngẫu nhiên khác?


88
+1 Đây là một câu hỏi hoàn toàn chính đáng. Trong thực tế, các giá trị chưa được khởi tạo có thể là ngẫu nhiên. Thực tế là họ không đặc biệt và rằng nó UB không làm cho hỏi xấu đó.
imallett

35
@imallett: Hoàn toàn đúng. Đây là một câu hỏi hay và ít nhất một trò chơi Z80 (Amstrad / ZX Spectrum) cũ trong thời gian qua đã sử dụng chương trình của nó làm dữ liệu để thiết lập địa hình. Vì vậy, thậm chí có tiền lệ. Không thể làm điều đó những ngày này. Hệ điều hành hiện đại lấy đi tất cả những niềm vui.
Bathsheba

81
Chắc chắn vấn đề chính là nó không ngẫu nhiên.
john

30
Trong thực tế, có một ví dụ về một biến chưa được khởi tạo đang được sử dụng làm giá trị ngẫu nhiên, xem thảm họa RNG Debian (Ví dụ 4 trong bài viết này ).
PaperBirdMaster

31
Trong thực tế - và tin tôi đi, tôi thực hiện rất nhiều việc sửa lỗi trên các kiến ​​trúc khác nhau - giải pháp của bạn có thể thực hiện hai việc: đọc các thanh ghi chưa được khởi tạo hoặc bộ nhớ chưa được khởi tạo. Bây giờ trong khi "uninitialized" có nghĩa là ngẫu nhiên theo một cách nhất định, trong thực tế, rất có thể nó sẽ chứa a) số 0 , b) lặp lại hoặc giá trị nhất quán (trong trường hợp đọc bộ nhớ trước đây bị chiếm bởi phương tiện kỹ thuật số) hoặc c) rác phù hợp với giá trị giới hạn thiết lập (trong trường hợp đọc bộ nhớ trước đây bị chiếm bởi dữ liệu số được mã hóa). Không ai trong số đó là nguồn entropy thực sự.
mg30rg

Câu trả lời:


299

Như những người khác đã lưu ý, đây là Hành vi không xác định (UB).

Trong thực tế, nó sẽ (có thể) thực sự (loại) hoạt động. Đọc từ một thanh ghi chưa được khởi tạo trên các kiến ​​trúc x86 [-64] thực sự sẽ tạo ra kết quả rác và có thể sẽ không làm gì xấu (trái ngược với Itanium, trong đó các thanh ghi có thể được gắn cờ là không hợp lệ , để đọc các lỗi lan truyền như NaN).

Có hai vấn đề chính:

  1. Nó sẽ không đặc biệt ngẫu nhiên. Trong trường hợp này, bạn đang đọc từ ngăn xếp, vì vậy bạn sẽ nhận được bất cứ thứ gì ở đó trước đó. Đó có thể là ngẫu nhiên, có cấu trúc hoàn toàn, mật khẩu bạn đã nhập mười phút trước hoặc công thức cookie của bà của bạn.

  2. Thực hành xấu (viết hoa 'B') để cho những thứ như thế này len vào mã của bạn. Về mặt kỹ thuật, trình biên dịch có thể chèn reformat_hdd();mỗi khi bạn đọc một biến không xác định. Nó sẽ không , nhưng dù sao bạn cũng không nên làm điều đó. Đừng làm những điều không an toàn. Bạn càng ít ngoại lệ, bạn càng an toàn trước những sai lầm vô tình mọi lúc.

Vấn đề cấp bách hơn với UB là nó làm cho toàn bộ hành vi của chương trình của bạn không được xác định. Các trình biên dịch hiện đại có thể sử dụng điều này để tạo ra các luồng lớn mã của bạn hoặc thậm chí quay ngược thời gian . Chơi với UB giống như một kỹ sư Victoria tháo dỡ lò phản ứng hạt nhân sống. Có một trăm điều sai lầm và có lẽ bạn sẽ không biết một nửa các nguyên tắc cơ bản hoặc công nghệ được triển khai. Nó thể ổn, nhưng bạn vẫn không nên để nó xảy ra. Nhìn vào các câu trả lời tốt đẹp khác để biết chi tiết.

Ngoài ra, tôi sẽ sa thải bạn.


39
@Potatoswatter: Các thanh ghi Itanium có thể chứa NaT (Không phải là một thứ) mà thực tế là một "thanh ghi chưa được khởi tạo". Trên Itanium, đọc từ một đăng ký khi bạn chưa viết thư cho nó có thể hủy bỏ chương trình của bạn (đọc thêm về nó ở đây: blog.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx ). Vì vậy, có một lý do chính đáng tại sao đọc các giá trị chưa được khởi tạo là hành vi không xác định. Đây có lẽ cũng là một lý do khiến Itanium không được yêu thích lắm :)
tbleher

58
Tôi thực sự phản đối khái niệm "nó là loại công trình". Ngay cả khi nó là sự thật ngày hôm nay, nhưng thực tế không phải vậy, nó có thể thay đổi bất cứ lúc nào do trình biên dịch tích cực hơn. Trình biên dịch có thể thay thế bất kỳ đọc unreachable()và xóa một nửa chương trình của bạn. Điều này cũng xảy ra trong thực tế. Hành vi này đã vô hiệu hóa hoàn toàn RNG trong một số bản phân phối Linux mà tôi tin.; Hầu hết các câu trả lời trong câu hỏi này dường như cho rằng một giá trị chưa được khởi tạo giống như một giá trị. Điều đó là sai.
usr

25
Ngoài ra, tôi sẽ sa thải bạn có vẻ như là một điều khá ngớ ngẩn để nói, giả sử thực hành tốt điều này nên được bắt gặp khi xem xét mã, thảo luận và không bao giờ nên xảy ra nữa. Điều này chắc chắn nên được bắt vì chúng ta đang sử dụng cờ cảnh báo chính xác, phải không?
Shafik Yaghmour

17
@Michael Thật ra là vậy. Nếu một chương trình có hành vi không xác định tại bất kỳ điểm nào, trình biên dịch có thể tối ưu hóa chương trình của bạn theo cách ảnh hưởng đến mã trước khi gọi hành vi không xác định đó. Có nhiều bài viết và minh chứng về việc làm thế nào để tâm trí này có thể có được Đây là một bài khá hay: blog.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx (bao gồm bit trong tiêu chuẩn nói tất cả các cược được tắt nếu bất kỳ đường dẫn nào trong chương trình của bạn gọi UB)
Tom Tanner

19
Câu trả lời này nghe có vẻ như "gọi hành vi không xác định là xấu trong lý thuyết, nhưng nó sẽ không thực sự làm bạn tổn thương nhiều trong thực tế" . Sai rồi. Thu thập entropy từ một biểu thức sẽ khiến UB có thể (và có thể sẽ ) khiến tất cả các entropy được thu thập trước đó bị mất . Đây là một mối nguy hiểm nghiêm trọng.
Theodoros Chatzigiannakis

213

Hãy để tôi nói điều này rõ ràng: chúng tôi không gọi hành vi không xác định trong các chương trình của chúng tôi . Nó không bao giờ là một ý tưởng tốt, thời gian. Có những trường hợp ngoại lệ hiếm hoi cho quy tắc này; ví dụ: nếu bạn là người triển khai thư viện triển khai offset . Nếu trường hợp của bạn thuộc một ngoại lệ như vậy, bạn có thể biết điều này rồi. Trong trường hợp này, chúng tôi biết sử dụng các biến tự động chưa được khởi tạo là hành vi không xác định .

Trình biên dịch đã trở nên rất tích cực với việc tối ưu hóa xung quanh hành vi không xác định và chúng ta có thể tìm thấy nhiều trường hợp trong đó hành vi không xác định đã dẫn đến lỗi bảo mật. Trường hợp khét tiếng nhất có lẽ là loại bỏ kiểm tra con trỏ null nhân Linux mà tôi đề cập trong câu trả lời của tôi về lỗi biên dịch C ++? trong đó tối ưu hóa trình biên dịch xung quanh hành vi không xác định đã biến một vòng lặp hữu hạn thành một vòng lặp vô hạn.

Chúng tôi có thể đọc Tối ưu hóa nguy hiểm của CERT và Mất nhân quả ( video ), trong số những điều khác:

Càng ngày, các nhà văn trình biên dịch càng lợi dụng các hành vi không xác định trong các ngôn ngữ lập trình C và C ++ để cải thiện tối ưu hóa.

Thông thường, những tối ưu hóa này đang cản trở khả năng của các nhà phát triển thực hiện phân tích nguyên nhân - kết quả trên mã nguồn của họ, nghĩa là phân tích sự phụ thuộc của kết quả hạ nguồn vào kết quả trước đó.

Do đó, những tối ưu hóa này đang loại bỏ tính nhân quả trong phần mềm và làm tăng xác suất lỗi, lỗi và lỗ hổng phần mềm.

Cụ thể liên quan đến các giá trị không xác định, báo cáo lỗi tiêu chuẩn C 451: Sự không ổn định của các biến tự động chưa được khởi tạo tạo ra một số cách đọc thú vị. Nó chưa được giải quyết nhưng đưa ra khái niệm về các giá trị chao đảo , có nghĩa là sự không xác định của một giá trị có thể lan truyền qua chương trình và có thể có các giá trị không xác định khác nhau tại các điểm khác nhau trong chương trình.

Tôi không biết bất kỳ ví dụ nào xảy ra nhưng tại thời điểm này, chúng tôi không thể loại trừ.

Ví dụ thực tế, không phải là kết quả mà bạn mong đợi

Bạn không có khả năng để có được giá trị ngẫu nhiên. Một trình biên dịch có thể tối ưu hóa hoàn toàn vòng lặp đi. Ví dụ, với trường hợp đơn giản này:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r ;
    }
}

clang tối ưu hóa nó đi ( xem nó sống ):

updateEffect(int*):                     # @updateEffect(int*)
    retq

hoặc có thể nhận được tất cả các số không, như với trường hợp sửa đổi này:

void updateEffect(int  arr[20]){
    for(int i=0;i<20;i++){
        int r ;    
        arr[i] = r%255 ;
    }
}

xem nó trực tiếp :

updateEffect(int*):                     # @updateEffect(int*)
    xorps   %xmm0, %xmm0
    movups  %xmm0, 64(%rdi)
    movups  %xmm0, 48(%rdi)
    movups  %xmm0, 32(%rdi)
    movups  %xmm0, 16(%rdi)
    movups  %xmm0, (%rdi)
    retq

Cả hai trường hợp này đều là những hình thức hoàn toàn chấp nhận được của hành vi không xác định.

Lưu ý, nếu chúng ta sử dụng Itanium, chúng ta có thể có giá trị bẫy :

[...] Nếu thanh ghi tình cờ giữ một giá trị không phải là thứ đặc biệt, hãy đọc các bẫy đăng ký ngoại trừ một vài hướng dẫn [...]

Ghi chú quan trọng khác

Thật thú vị khi lưu ý sự khác biệt giữa gcc và clang được ghi nhận trong dự án UB Canaries về việc họ sẵn sàng tận dụng hành vi không xác định như thế nào đối với bộ nhớ chưa được khởi tạo. Bài viết ghi chú ( nhấn mạnh của tôi ):

Tất nhiên chúng ta cần phải hoàn toàn rõ ràng với chính mình rằng mọi kỳ vọng như vậy không liên quan gì đến tiêu chuẩn ngôn ngữ và mọi thứ phải làm với những gì một trình biên dịch cụ thể xảy ra, vì các nhà cung cấp trình biên dịch đó không muốn khai thác UB đó hoặc chỉ bởi vì họ chưa đi khai thác nó . Khi không có sự bảo đảm thực sự từ nhà cung cấp trình biên dịch, chúng tôi muốn nói rằng các UB chưa được khai thác là những quả bom hẹn giờ : họ đang chờ đợi để tắt vào tháng tới hoặc năm tới khi trình biên dịch trở nên hung hăng hơn một chút.

Như Matthieu M. chỉ ra những gì mỗi lập trình viên C nên biết về hành vi không xác định # 2/3 cũng có liên quan đến câu hỏi này. Nó nói trong số những thứ khác ( nhấn mạnh của tôi ):

Điều quan trọng và đáng sợ để nhận ra là chỉ là về bất kỳ tối ưu hóa dựa trên hành vi không xác định có thể bắt đầu được kích hoạt trên mã lỗi bất cứ lúc nào trong tương lai . Nội tuyến, không kiểm soát vòng lặp, thúc đẩy bộ nhớ và các tối ưu hóa khác sẽ tiếp tục tốt hơn và một phần lý do quan trọng của chúng hiện tại là để lộ các tối ưu hóa thứ cấp như các tối ưu hóa ở trên.

Đối với tôi, điều này thật không hài lòng, một phần vì trình biên dịch chắc chắn cuối cùng bị đổ lỗi, nhưng cũng bởi vì điều đó có nghĩa là các khối mã C khổng lồ là mỏ đất chỉ chờ nổ tung.

Để hoàn thiện hơn, có lẽ tôi nên đề cập rằng việc triển khai có thể chọn làm cho hành vi không xác định được xác định rõ, ví dụ gcc cho phép gõ thông qua các hiệp trong khi trong C ++, điều này có vẻ giống như hành vi không xác định . Nếu đây là trường hợp thực hiện nên ghi lại nó và điều này thường sẽ không thể mang theo được.


1
+ (int) (PI / 3) cho các ví dụ đầu ra của trình biên dịch; một ví dụ thực tế mà UB là, tốt, UB .

2
Sử dụng UB hiệu quả được sử dụng là thương hiệu của một hacker xuất sắc. Truyền thống này đã có từ 50 năm trở lên. Thật không may, máy tính hiện đang được yêu cầu để giảm thiểu ảnh hưởng của UB vì Người xấu. Tôi thực sự rất thích tìm ra cách làm những điều tuyệt vời với mã máy UB hoặc đọc / ghi cổng, v.v. Tôi những năm 90, khi HĐH không có khả năng tự bảo vệ người dùng khỏi chính họ.
sfdcfox

1
@sfdcfox nếu bạn đang thực hiện nó trong mã máy / trình biên dịch chương trình, thì đó không phải là hành vi không xác định (nó có thể là hành vi độc đáo).
Caleth

2
Nếu bạn có một hội đồng cụ thể trong tâm trí, thì hãy sử dụng nó và đừng viết không hoàn hảo C. Sau đó mọi người sẽ biết bạn đang sử dụng một thủ thuật không di động cụ thể. Và đó không phải là những người xấu, những người có nghĩa là bạn không thể sử dụng UB, đó là Intel, v.v.
Caleth

2
@ 500-InternalServerError vì chúng có thể không dễ dàng phát hiện hoặc có thể không phát hiện được trong trường hợp chung và do đó sẽ không có cách nào để không cho phép chúng. Đó là khác nhau sau đó vi phạm ngữ pháp có thể được phát hiện. Chúng tôi cũng không có chẩn đoán hình thành và không hình thành mà không cần chẩn đoán mà nói chung tách các chương trình hình thành kém có thể được phát hiện trong lý thuyết từ những chương trình mà về lý thuyết không thể phát hiện được một cách đáng tin cậy.
Shafik Yaghmour

164

Không, nó thật kinh khủng.

Hành vi sử dụng một biến chưa được khởi tạo không được xác định trong cả C và C ++ và rất khó có khả năng một sơ đồ như vậy có các thuộc tính thống kê mong muốn.

Nếu bạn muốn một trình tạo số ngẫu nhiên "nhanh và bẩn", thì đó rand()là cách tốt nhất của bạn. Trong quá trình thực hiện, tất cả những gì nó làm là phép nhân, phép cộng và mô đun.

Trình tạo nhanh nhất mà tôi biết yêu cầu bạn sử dụng uint32_tnhư một loại biến giả ngẫu nhiên Ivà sử dụng

I = 1664525 * I + 1013904223

để tạo ra các giá trị liên tiếp. Bạn có thể chọn bất kỳ giá trị ban đầu nào I(được gọi là hạt giống ) mà bạn thích. Rõ ràng bạn có thể mã hóa nội tuyến đó. Gói bảo đảm tiêu chuẩn của loại không dấu đóng vai trò là mô đun. (Các hằng số được chọn bởi nhà lập trình khoa học đáng chú ý Donald Knuth.)


9
Trình tạo "đồng quy tuyến tính" mà bạn trình bày là tốt cho các ứng dụng đơn giản, nhưng chỉ dành cho các ứng dụng không mã hóa. Có thể dự đoán hành vi của nó. Xem ví dụ " Giải mã mã hóa tương đương tuyến tính " của chính Don Knuth (Giao dịch của IEEE về Lý thuyết thông tin, Tập 31)
Jay

24
@Jay so với một biến đơn vị cho nhanh và bẩn? Đây là một giải pháp tốt hơn nhiều.
Mike McMahon

2
rand()không phù hợp với mục đích và theo tôi là hoàn toàn không được chấp nhận Ngày nay, bạn có thể tải xuống các trình tạo số ngẫu nhiên được cấp phép miễn phí và cực kỳ vượt trội (ví dụ: Mersenne Twister) rất nhanh với mức độ dễ dàng nhất vì vậy thực sự không cần phải tiếp tục sử dụng lỗi rất caorand()
Jack Aidley

1
rand () có một vấn đề khủng khiếp khác: nó sử dụng một loại khóa, được gọi là các luồng bên trong nó làm chậm mã của bạn một cách đáng kể. Ít nhất, có một phiên bản reentrant. Và nếu bạn sử dụng C ++ 11, API ngẫu nhiên cung cấp mọi thứ bạn cần.
Marwan Burelle

4
Để công bằng, ông đã không hỏi nếu nó là một trình tạo số ngẫu nhiên tốt. Anh hỏi nó có nhanh không. Vâng, vâng, có lẽ là nhịn ăn., Nhưng kết quả sẽ không ngẫu nhiên chút nào.
jcoder

42

Câu hỏi hay!

Không xác định không có nghĩa là nó ngẫu nhiên. Hãy suy nghĩ về nó, các giá trị bạn nhận được trong các biến chưa được khởi tạo toàn cầu đã được hệ thống hoặc các ứng dụng khác của bạn chạy. Tùy thuộc vào những gì hệ thống của bạn làm với bộ nhớ không còn sử dụng và / hoặc loại giá trị nào mà hệ thống và ứng dụng tạo ra, bạn có thể nhận được:

  1. Luôn luôn giống nhau.
  2. Hãy là một trong những tập hợp nhỏ của các giá trị.
  3. Nhận giá trị trong một hoặc nhiều phạm vi nhỏ.
  4. Xem nhiều giá trị chia cho 2/4/8 từ các con trỏ trên hệ thống 16/32/64-bit
  5. ...

Các giá trị bạn sẽ nhận được hoàn toàn phụ thuộc vào các giá trị không ngẫu nhiên còn lại của hệ thống và / hoặc ứng dụng. Vì vậy, thực sự sẽ có một số tiếng ồn (trừ khi hệ thống của bạn không còn sử dụng bộ nhớ nữa), nhưng nhóm giá trị mà bạn sẽ rút ra sẽ không phải là ngẫu nhiên.

Mọi thứ trở nên tồi tệ hơn nhiều đối với các biến cục bộ vì chúng xuất phát trực tiếp từ ngăn xếp chương trình của riêng bạn. Có một cơ hội rất tốt rằng chương trình của bạn sẽ thực sự viết các vị trí ngăn xếp này trong quá trình thực thi mã khác. Tôi ước tính cơ hội may mắn trong tình huống này rất thấp và thay đổi mã 'ngẫu nhiên' mà bạn thực hiện thử vận ​​may này.

Đọc về sự ngẫu nhiên . Như bạn sẽ thấy tính ngẫu nhiên là một tài sản rất cụ thể và khó có được. Đó là một sai lầm phổ biến khi nghĩ rằng nếu bạn chỉ lấy thứ gì đó khó theo dõi (như đề xuất của bạn), bạn sẽ nhận được một giá trị ngẫu nhiên.


7
... và đó là loại bỏ tất cả các tối ưu hóa trình biên dịch sẽ hoàn toàn rút ra mã đó.
Ded repeatator

6 ... Bạn sẽ nhận được "tính ngẫu nhiên" khác nhau trong Gỡ lỗi và Phát hành. Không xác định có nghĩa là bạn đang làm sai.
Sql Surfer

Đúng. Tôi viết tắt hoặc tóm tắt bằng "không xác định"! = "Tùy ý"! = "Ngẫu nhiên". Tất cả các loại "không xác định" có tính chất khác nhau.
fche

Các biến toàn cục được đảm bảo có giá trị xác định, cho dù được khởi tạo rõ ràng hay không. Điều này chắc chắn đúng trong C ++C cũng vậy .
Brian Vandenberg

32

Nhiều câu trả lời hay, nhưng cho phép tôi thêm một câu trả lời khác và nhấn mạnh rằng trong một máy tính xác định, không có gì là ngẫu nhiên. Điều này đúng cho cả các số được tạo bởi một RNG giả và các số dường như "ngẫu nhiên" được tìm thấy trong các vùng của bộ nhớ dành cho các biến cục bộ C / C ++ trên ngăn xếp.

NHƯNG ... có một sự khác biệt quan trọng.

Các số được tạo bởi một trình tạo giả ngẫu nhiên tốt có các thuộc tính làm cho chúng giống nhau về mặt thống kê với các lần rút ngẫu nhiên thực sự. Ví dụ, phân phối là thống nhất. Độ dài chu kỳ dài: bạn có thể nhận được hàng triệu số ngẫu nhiên trước khi chu kỳ lặp lại. Chuỗi không được tự động tương quan: ví dụ, bạn sẽ không bắt đầu thấy các mẫu lạ xuất hiện nếu bạn lấy mỗi số thứ 2, 3 hoặc 27 hoặc nếu bạn nhìn vào các chữ số cụ thể trong các số được tạo.

Ngược lại, các số "ngẫu nhiên" bị bỏ lại trên ngăn xếp không có thuộc tính nào trong số này. Giá trị của chúng và tính ngẫu nhiên rõ ràng của chúng phụ thuộc hoàn toàn vào cách chương trình được xây dựng, cách nó được biên dịch và cách nó được tối ưu hóa bởi trình biên dịch. Ví dụ, đây là một biến thể của ý tưởng của bạn dưới dạng một chương trình độc lập:

#include <stdio.h>

notrandom()
{
        int r, g, b;

        printf("R=%d, G=%d, B=%d", r&255, g&255, b&255);
}

int main(int argc, char *argv[])
{
        int i;
        for (i = 0; i < 10; i++)
        {
                notrandom();
                printf("\n");
        }

        return 0;
}

Khi tôi biên dịch mã này với GCC trên máy Linux và chạy nó, hóa ra nó có tính quyết định khá khó chịu:

R=0, G=19, B=0
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255
R=130, G=16, B=255

Nếu bạn đã xem mã được biên dịch bằng trình dịch ngược, bạn có thể xây dựng lại những gì đang diễn ra, một cách chi tiết. Cuộc gọi đầu tiên đến notrandom () đã sử dụng một vùng của ngăn xếp mà chương trình này không sử dụng trước đó; Ai biết cái gì ở đó. Nhưng sau cuộc gọi đó đến notrandom (), có một lệnh gọi printf () (mà trình biên dịch GCC thực sự tối ưu hóa cho một lệnh gọi tới putchar (), nhưng đừng bận tâm) và ghi đè lên ngăn xếp. Vì vậy, lần tiếp theo và lần tiếp theo, khi notrandom () được gọi, ngăn xếp sẽ chứa dữ liệu cũ từ việc thực hiện putchar () và vì putchar () luôn được gọi với cùng các đối số, dữ liệu cũ này sẽ luôn giống nhau, quá.

Vì vậy, hoàn toàn không có gì ngẫu nhiên về hành vi này, cũng như các con số thu được theo cách này có bất kỳ tính chất mong muốn nào của trình tạo số giả ngẫu nhiên được viết tốt. Trong thực tế, trong hầu hết các kịch bản thực tế, giá trị của chúng sẽ lặp đi lặp lại và có mối tương quan cao.

Thật vậy, như những người khác, tôi cũng sẽ nghiêm túc xem xét việc sa thải ai đó đã cố gắng loại bỏ ý tưởng này như là một "RNG hiệu suất cao".


1
Một máy tính xác định, không có gì là ngẫu nhiên. Điều này không thực sự đúng. Các máy tính hiện đại chứa tất cả các loại cảm biến cho phép bạn tạo ra sự ngẫu nhiên thực sự , không thể đoán trước mà không cần các bộ tạo phần cứng riêng biệt. Trên một kiến ​​trúc hiện đại, các giá trị /dev/randomthường được lấy từ các nguồn phần cứng như vậy, và trên thực tế, tiếng ồn lượng tử của Google, tức là thực sự không thể đoán trước theo nghĩa vật lý tốt nhất của từ này.
Konrad Rudolph

2
Nhưng sau đó, đó không phải là một máy tính xác định, phải không? Bây giờ bạn đang dựa vào đầu vào môi trường. Trong mọi trường hợp, điều này đưa chúng ta vượt ra khỏi cuộc thảo luận về các bit giả RNG thông thường so với các bit "ngẫu nhiên" trong bộ nhớ chưa được khởi tạo. Ngoài ra ... hãy xem mô tả của / dev / ngẫu nhiên để đánh giá cao những người triển khai đã đi xa đến mức nào để đảm bảo rằng các số ngẫu nhiên được bảo mật bằng mật mã ... chính xác vì các nguồn đầu vào không phải là nhiễu lượng tử thuần túy, không tương quan nhưng thay vào đó, khả năng đọc cảm biến có khả năng tương quan cao chỉ với một mức độ ngẫu nhiên nhỏ. Nó cũng khá chậm.
Viktor Toth

29

Hành vi không xác định có nghĩa là các tác giả của trình biên dịch có thể tự do bỏ qua vấn đề vì các lập trình viên sẽ không bao giờ có quyền khiếu nại bất cứ điều gì xảy ra.

Mặc dù về lý thuyết khi vào đất UB, bất cứ điều gì cũng có thể xảy ra (bao gồm cả daemon bay ra khỏi mũi của bạn ), điều thường có nghĩa là các tác giả trình biên dịch sẽ không quan tâm và, đối với các biến cục bộ, giá trị sẽ là bất cứ thứ gì trong bộ nhớ stack tại thời điểm đó .

Điều này cũng có nghĩa là thường nội dung sẽ "lạ" nhưng cố định hoặc hơi ngẫu nhiên hoặc biến đổi nhưng có mẫu rõ ràng (ví dụ: tăng giá trị ở mỗi lần lặp).

Để chắc chắn bạn không thể mong đợi nó là một máy phát ngẫu nhiên phong nha.


28

Hành vi không xác định là không xác định. Điều đó không có nghĩa là bạn nhận được một giá trị không xác định, điều đó có nghĩa là chương trình có thể làm bất cứ điều gì và vẫn đáp ứng các đặc điểm kỹ thuật ngôn ngữ.

Một trình biên dịch tối ưu hóa tốt nên dùng

void updateEffect(){
    for(int i=0;i<1000;i++){
        int r;
        int g;
        int b;
        star[i].setColor(r%255,g%255,b%255);
        bool isVisible;
        star[i].setVisible(isVisible);
    }
}

và biên dịch nó thành một noop. Điều này chắc chắn là nhanh hơn bất kỳ thay thế. Nó có nhược điểm là nó sẽ không làm gì cả, nhưng đó là nhược điểm của hành vi không xác định.


3
Nhiều thứ phụ thuộc vào mục đích của trình biên dịch có giúp các lập trình viên tạo ra các tệp thực thi đáp ứng các yêu cầu miền hay không, hay mục đích là tạo ra tệp thực thi "hiệu quả" nhất mà hành vi của nó sẽ phù hợp với các yêu cầu tối thiểu của Tiêu chuẩn C, mà không liên quan đến việc liệu hành vi đó sẽ phục vụ bất kỳ mục đích hữu ích. Đối với mục tiêu trước đây, việc có mã sử dụng một số giá trị ban đầu tùy ý cho r, g, b hoặc kích hoạt bẫy gỡ lỗi nếu thực tế, sẽ hữu ích hơn là biến mã thành nop. Liên quan đến mục tiêu thứ hai ...
supercat

2
... một trình biên dịch tối ưu sẽ xác định đầu vào nào sẽ khiến phương thức trên thực thi và loại bỏ bất kỳ mã nào chỉ có liên quan khi các đầu vào đó được nhận.
supercat

1
@supercat Hoặc mục đích của nó có thể là C. để tạo ra các tệp thực thi hiệu quả tuân thủ Tiêu chuẩn đồng thời giúp lập trình viên tìm những nơi mà việc tuân thủ có thể không hữu ích. Trình biên dịch có thể đáp ứng mục đích thỏa hiệp này bằng cách phát ra nhiều chẩn đoán hơn so với Tiêu chuẩn yêu cầu, chẳng hạn như của GCC -Wall -Wextra.
Damian Yerrick

1
Các giá trị không được xác định không có nghĩa là hành vi của mã xung quanh không được xác định. Không có trình biên dịch nên noop chức năng đó. Hai lệnh gọi hàm, bất kỳ đầu vào nào chúng được cung cấp, tuyệt đối PHẢI được gọi; số đầu tiên PHẢI được gọi với ba số trong khoảng từ 0 đến 255 và số thứ hai PHẢI được gọi với giá trị đúng hoặc sai. Một "trình biên dịch tối ưu hóa tốt" có thể tối ưu hóa các tham số hàm thành các giá trị tĩnh tùy ý, loại bỏ hoàn toàn các biến, nhưng đó là tất cả những gì có thể xảy ra (tốt, trừ khi chính các hàm có thể bị giảm xuống mức không có trên các đầu vào nhất định).
Dewi Morgan

@DewiMorgan - vì các hàm được gọi là loại "đặt tham số này", chúng gần như chắc chắn giảm xuống mức không có khi đầu vào giống như giá trị hiện tại của tham số, mà trình biên dịch có thể giả sử là trường hợp.
Jules

18

Chưa được đề cập, nhưng các đường dẫn mã gọi hành vi không xác định được phép làm bất cứ điều gì trình biên dịch muốn, vd

void updateEffect(){}

Cái nào chắc chắn nhanh hơn vòng lặp chính xác của bạn và vì UB, hoàn toàn phù hợp.


18

Vì lý do bảo mật, bộ nhớ mới được gán cho chương trình phải được dọn sạch, nếu không thông tin có thể được sử dụng và mật khẩu có thể bị rò rỉ từ ứng dụng này sang ứng dụng khác. Chỉ khi bạn sử dụng lại bộ nhớ, bạn mới nhận được các giá trị khác 0. Và rất có thể, trên một ngăn xếp, giá trị trước đó chỉ là cố định, bởi vì việc sử dụng bộ nhớ đó trước đó là cố định.


13

Ví dụ mã cụ thể của bạn có thể sẽ không làm những gì bạn đang mong đợi. Về mặt kỹ thuật, mỗi lần lặp của vòng lặp sẽ tạo lại các biến cục bộ cho các giá trị r, g và b, trong thực tế, đó là cùng một không gian bộ nhớ chính xác trên ngăn xếp. Do đó, nó sẽ không được ngẫu nhiên hóa lại với mỗi lần lặp và cuối cùng bạn sẽ gán 3 giá trị giống nhau cho mỗi 1000 màu, bất kể r, g và b ngẫu nhiên như thế nào là riêng lẻ và ban đầu.

Thật vậy, nếu nó hoạt động, tôi sẽ rất tò mò về những gì tái ngẫu nhiên hóa nó. Điều duy nhất tôi có thể nghĩ đến sẽ là một ngắt xen kẽ mà cõng trên đỉnh ngăn xếp đó, rất khó xảy ra. Có lẽ tối ưu hóa nội bộ giữ các biến đó là biến đăng ký thay vì vị trí bộ nhớ thực, trong đó các thanh ghi được sử dụng lại trong vòng lặp, cũng sẽ thực hiện thủ thuật, đặc biệt là nếu chức năng hiển thị được thiết lập đặc biệt đói đăng ký. Tuy nhiên, xa ngẫu nhiên.


12

Như hầu hết mọi người ở đây đề cập đến hành vi không xác định. Không xác định cũng có nghĩa là bạn có thể nhận được một số giá trị nguyên hợp lệ (may mắn thay) và trong trường hợp này sẽ nhanh hơn (vì lệnh gọi hàm rand không được thực hiện). Nhưng thực tế không sử dụng nó. Tôi chắc chắn điều này sẽ mang lại kết quả khủng khiếp vì may mắn không phải lúc nào cũng ở bên bạn.


1
Điểm rất tốt! Nó có thể là một mẹo thực dụng, nhưng thực sự là một đòi hỏi may mắn.
ý nghĩa-vấn đề

1
Hoàn toàn không có may mắn liên quan. Nếu trình biên dịch không tối ưu hóa hành vi không xác định đi, các giá trị bạn nhận được sẽ hoàn toàn xác định (= phụ thuộc hoàn toàn vào chương trình của bạn, đầu vào của nó, trình biên dịch của nó, các thư viện mà nó sử dụng, thời gian của các luồng nếu nó có các luồng). Vấn đề là bạn không thể suy luận về các giá trị này vì chúng phụ thuộc vào chi tiết triển khai.
cmaster - phục hồi monica

Trong trường hợp không có hệ điều hành với ngăn xếp xử lý ngắt tách biệt với ngăn xếp ứng dụng, may mắn có thể được tham gia, vì các ngắt sẽ thường làm xáo trộn nội dung của bộ nhớ ngoài các nội dung ngăn xếp hiện tại.
supercat

12

Thực sự tồi tệ! Thói quen xấu, kết quả xấu. Xem xét:

A_Function_that_use_a_lot_the_Stack();
updateEffect();

Nếu hàm A_Function_that_use_a_lot_the_Stack()luôn luôn khởi tạo giống nhau, nó sẽ rời khỏi ngăn xếp với cùng dữ liệu trên nó. Dữ liệu đó là những gì chúng ta nhận được gọi updateEffect(): luôn luôn có giá trị! .


11

Tôi đã thực hiện một thử nghiệm rất đơn giản và nó hoàn toàn không ngẫu nhiên.

#include <stdio.h>

int main() {

    int a;
    printf("%d\n", a);
    return 0;
}

Mỗi lần tôi chạy chương trình, nó đều in cùng một số ( 32767trong trường hợp của tôi) - bạn không thể nhận được ít ngẫu nhiên hơn thế. Đây có lẽ là bất cứ điều gì mã khởi động trong thư viện thời gian chạy còn lại trên ngăn xếp. Vì nó sử dụng cùng một mã khởi động mỗi khi chương trình chạy và không có gì khác trong chương trình giữa các lần chạy, nên kết quả hoàn toàn phù hợp.


Điểm tốt. Một kết quả phụ thuộc mạnh mẽ vào nơi mà trình tạo số "ngẫu nhiên" này được gọi trong mã. Nó là khá khó lường hơn ngẫu nhiên.
NO_NAME

10

Bạn cần có một định nghĩa về ý nghĩa của 'ngẫu nhiên'. Một định nghĩa hợp lý liên quan đến việc các giá trị bạn nhận được nên có ít mối tương quan. Đó là thứ bạn có thể đo được. Nó cũng không tầm thường để đạt được một cách có thể kiểm soát, tái sản xuất. Vì vậy, hành vi không xác định chắc chắn không phải là những gì bạn đang tìm kiếm.


7

Có một số tình huống trong đó bộ nhớ chưa được khởi tạo có thể được đọc một cách an toàn bằng cách sử dụng loại "unsign char *" [ví dụ: bộ đệm được trả về từ malloc]. Mã có thể đọc bộ nhớ như vậy mà không phải lo lắng về trình biên dịch ném nhân quả ra ngoài cửa sổ, và đôi khi việc chuẩn bị mã cho bất cứ thứ gì bộ nhớ có thể hiệu quả hơn là để đảm bảo rằng dữ liệu chưa được khởi tạo sẽ không được đọc ( một ví dụ phổ biến về điều này sẽ được sử dụng memcpytrên bộ đệm được khởi tạo một phần thay vì sao chép một cách riêng biệt tất cả các yếu tố có chứa dữ liệu có ý nghĩa).

Tuy nhiên, ngay cả trong những trường hợp như vậy, người ta luôn phải cho rằng nếu bất kỳ tổ hợp byte nào sẽ đặc biệt khó chịu, thì việc đọc nó sẽ luôn mang lại mô hình byte đó (và nếu một mẫu nhất định sẽ gây khó chịu trong sản xuất, nhưng không phát triển, như vậy mẫu sẽ không xuất hiện cho đến khi mã được sản xuất).

Đọc bộ nhớ chưa được khởi tạo có thể hữu ích như là một phần của chiến lược tạo ngẫu nhiên trong một hệ thống nhúng, trong đó người ta có thể chắc chắn rằng bộ nhớ chưa bao giờ được ghi với nội dung không ngẫu nhiên kể từ lần cuối cùng hệ thống được bật và nếu sản xuất quá trình được sử dụng cho bộ nhớ khiến trạng thái bật nguồn của nó thay đổi theo kiểu bán ngẫu nhiên. Mã phải hoạt động ngay cả khi tất cả các thiết bị luôn mang lại cùng một dữ liệu, nhưng trong trường hợp ví dụ như một nhóm các nút, mỗi ID cần chọn ID duy nhất tùy ý càng nhanh càng tốt, có một trình tạo "không ngẫu nhiên" cung cấp cho một nửa các nút giống nhau ID có thể tốt hơn là không có bất kỳ nguồn ngẫu nhiên ban đầu nào cả.


2
"Nếu bất kỳ sự kết hợp nào của byte sẽ đặc biệt khó chịu, thì việc đọc nó sẽ luôn mang lại mô hình byte đó" - cho đến khi bạn mã để đối phó với mẫu đó, tại thời điểm đó nó sẽ không còn khó chịu nữa và một mẫu khác sẽ được đọc trong tương lai.
Steve Jessop

@SteveJessop: Chính xác. Dòng của tôi về phát triển và sản xuất đã được dự định để truyền đạt một khái niệm tương tự. Mã không nên quan tâm đến những gì trong bộ nhớ chưa được khởi tạo ngoài một khái niệm mơ hồ về "Một số ngẫu nhiên có thể là tốt đẹp". Nếu hành vi của chương trình bị ảnh hưởng bởi nội dung của một phần của bộ nhớ chưa được khởi tạo, thì nội dung của các phần được thu thập trong tương lai có thể bị ảnh hưởng bởi điều đó.
supercat

5

Như những người khác đã nói, nó sẽ nhanh, nhưng không ngẫu nhiên.

Điều mà hầu hết các trình biên dịch sẽ làm cho các biến cục bộ là lấy một khoảng trống cho chúng trên ngăn xếp, nhưng không bận tâm đặt nó thành bất cứ thứ gì (tiêu chuẩn nói rằng chúng không cần, vậy tại sao làm chậm mã bạn đang tạo?).

Trong trường hợp này, giá trị bạn sẽ nhận được sẽ phụ thuộc vào giá trị trước đó trên ngăn xếp - nếu bạn gọi một hàm trước hàm này có hàng trăm biến char cục bộ được đặt thành 'Q' và sau đó gọi hàm của bạn là sau trả về, sau đó có thể bạn sẽ thấy các giá trị "ngẫu nhiên" của mình hoạt động như thể memset()tất cả các bạn đều thuộc về 'Q.

Quan trọng đối với chức năng ví dụ của bạn đang cố gắng sử dụng điều này, các giá trị này sẽ không thay đổi mỗi khi bạn đọc chúng, chúng sẽ giống nhau mỗi lần. Vì vậy, bạn sẽ nhận được 100 sao tất cả được đặt cùng màu và khả năng hiển thị.

Ngoài ra, không có gì nói rằng trình biên dịch không nên khởi tạo các giá trị này - vì vậy trình biên dịch trong tương lai có thể làm như vậy.

Nói chung: ý tưởng tồi, đừng làm điều đó. (giống như rất nhiều tối ưu hóa mức mã "thông minh" thực sự ...)


2
Bạn đang đưa ra một số dự đoán mạnh mẽ về những gì sẽ xảy ra mặc dù điều đó không được đảm bảo do UB. Nó cũng không đúng trong thực tế.
usr

3

Như những người khác đã đề cập, đây là hành vi không xác định ( UB ), nhưng nó có thể "hoạt động".

Ngoại trừ các vấn đề đã được đề cập bởi những người khác, tôi thấy một vấn đề khác (bất lợi) - nó sẽ không hoạt động trong bất kỳ ngôn ngữ nào ngoài C và C ++. Tôi biết rằng câu hỏi này là về C ++, nhưng nếu bạn có thể viết mã sẽ là mã C ++ và Java tốt và đó không phải là vấn đề thì tại sao không? Có thể một ngày nào đó ai đó sẽ phải chuyển nó sang ngôn ngữ khác và tìm kiếm các lỗi gây ra bởi "trò ảo thuật" UB như thế này chắc chắn sẽ là một cơn ác mộng (đặc biệt đối với một nhà phát triển C / C ++ thiếu kinh nghiệm).

Ở đây có câu hỏi về một UB tương tự khác. Chỉ cần tưởng tượng bạn đang cố gắng tìm lỗi như thế này mà không biết về UB này. Nếu bạn muốn đọc thêm về những điều kỳ lạ như vậy trong C / C ++, hãy đọc câu trả lời cho câu hỏi từ liên kết và xem slideshow này TUYỆT VỜI . Nó sẽ giúp bạn hiểu những gì dưới mui xe và cách nó hoạt động; nó không chỉ là một slideshow đầy "ma thuật". Tôi khá chắc chắn rằng ngay cả hầu hết các lập trình viên C / c ++ có kinh nghiệm đều có thể học được nhiều điều từ việc này.


3

Không phải là một ý tưởng tốt để dựa vào bất kỳ logic của chúng tôi về hành vi không xác định ngôn ngữ. Ngoài bất cứ điều gì được đề cập / thảo luận trong bài đăng này, tôi muốn đề cập rằng với cách tiếp cận / phong cách C ++ hiện đại, chương trình như vậy có thể không được biên dịch.

Điều này đã được đề cập trong bài viết trước của tôi có chứa lợi thế của tính năng tự động và liên kết hữu ích cho cùng.

https://stackoverflow.com/a/26170069/2724703

Vì vậy, nếu chúng ta thay đổi mã trên và thay thế các loại thực tế bằng tự động , chương trình thậm chí sẽ không biên dịch.

void updateEffect(){
    for(int i=0;i<1000;i++){
        auto r;
        auto g;
        auto b;
        star[i].setColor(r%255,g%255,b%255);
        auto isVisible;
        star[i].setVisible(isVisible);
    }
}

3

Tôi thích cách suy nghĩ của bạn. Thực sự bên ngoài hộp. Tuy nhiên sự đánh đổi thực sự không đáng. Trao đổi bộ nhớ thời gian chạy là một điều, bao gồm cả hành vi không xác định cho thời gian chạy là không .

Nó phải mang lại cho bạn cảm giác rất đáng lo ngại khi biết bạn đang sử dụng "ngẫu nhiên" như logic kinh doanh của mình. Tôi sẽ không làm điều đó.


3

Sử dụng 7757mọi nơi bạn muốn sử dụng các biến chưa được khởi tạo. Tôi chọn nó ngẫu nhiên từ một danh sách các số nguyên tố:

  1. đó là hành vi được xác định

  2. nó được đảm bảo không phải luôn luôn là 0

  3. nó là nguyên tố

  4. nó có khả năng là ngẫu nhiên thống kê như các biến chưa được khởi tạo

  5. nó có khả năng nhanh hơn các biến chưa được khởi tạo do giá trị của nó được biết tại thời điểm biên dịch


Để so sánh, hãy xem kết quả trong câu trả lời này: stackoverflow.com/a/31836461/2963099
Glenn Teitelbaum

1

Có một khả năng nữa để xem xét.

Trình biên dịch hiện đại (ahem g ++) thông minh đến mức họ duyệt mã của bạn để xem hướng dẫn nào ảnh hưởng đến trạng thái và điều gì không, và nếu một lệnh được đảm bảo KHÔNG ảnh hưởng đến trạng thái, g ++ sẽ xóa hướng dẫn đó.

Vì vậy, đây là những gì sẽ xảy ra. g ++ chắc chắn sẽ thấy rằng bạn đang đọc, thực hiện số học, lưu, về cơ bản là giá trị rác, tạo ra nhiều rác hơn. Vì không có gì đảm bảo rằng rác mới hữu ích hơn thùng rác cũ, nên nó sẽ đơn giản loại bỏ vòng lặp của bạn. BẮT ĐẦU!

Phương pháp này hữu ích, nhưng đây là những gì tôi sẽ làm. Kết hợp UB (Hành vi không xác định) với tốc độ rand ().

Tất nhiên, giảm rand()s thực thi, nhưng trộn chúng vào để trình biên dịch không làm bất cứ điều gì bạn không muốn.

Và tôi sẽ không sa thải bạn.


Tôi thấy rất khó tin rằng một trình biên dịch có thể quyết định mã của bạn đang làm điều gì đó ngớ ngẩn và loại bỏ nó. Tôi hy vọng nó chỉ tối ưu hóa đi mã không sử dụng , không phải mã không thể truy cập . Bạn có một trường hợp kiểm tra tái sản xuất? Dù bằng cách nào, khuyến nghị của UB là nguy hiểm. Thêm vào đó, GCC không phải là trình biên dịch có thẩm quyền duy nhất xung quanh, vì vậy thật không công bằng khi coi nó là "hiện đại".
gạch dưới

-1

Sử dụng dữ liệu chưa được khởi tạo cho tính ngẫu nhiên không nhất thiết là điều xấu nếu được thực hiện đúng cách. Trong thực tế, OpenSSL thực hiện chính xác điều này để gieo mầm PRNG của nó.

Tuy nhiên, rõ ràng việc sử dụng này không được chứng minh rõ ràng, bởi vì ai đó nhận thấy Valgrind phàn nàn về việc sử dụng dữ liệu chưa được khởi tạo và "sửa" nó, gây ra lỗi trong PRNG .

Vì vậy, bạn có thể làm điều đó, nhưng bạn cần biết những gì bạn đang làm và đảm bảo rằng bất cứ ai đọc mã của bạn đều hiểu điều này.


1
Điều này sẽ phụ thuộc vào trình biên dịch của bạn được mong đợi với hành vi không xác định, như chúng ta có thể thấy từ câu trả lời của tôi hôm nay sẽ không làm những gì họ muốn.
Shafik Yaghmour

6
OpenSSL đã sử dụng phương thức này như một đầu vào entropy không nói rằng nó là bất kỳ tốt. Rốt cuộc, nguồn entropy duy nhất khác mà họ sử dụng là PID . Không chính xác một giá trị ngẫu nhiên tốt. Từ một người dựa vào nguồn entropy xấu như vậy, tôi sẽ không mong đợi sự phán xét tốt về nguồn entropy khác của họ. Tôi chỉ hy vọng, những người hiện đang duy trì OpenSSL sáng hơn.
cmaster - phục hồi monica
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.