Làm thế nào nguy hiểm là truy cập vào một mảng ngoài giới hạn?


221

Làm thế nào nguy hiểm là truy cập vào một mảng bên ngoài giới hạn của nó (trong C)? Đôi khi có thể xảy ra khi tôi đọc từ bên ngoài mảng (bây giờ tôi hiểu rằng sau đó tôi truy cập bộ nhớ được sử dụng bởi một số phần khác trong chương trình của tôi hoặc thậm chí ngoài điều đó) hoặc tôi đang cố gắng đặt giá trị cho một chỉ mục bên ngoài mảng. Chương trình đôi khi gặp sự cố, nhưng đôi khi chỉ chạy, chỉ cho kết quả bất ngờ.

Bây giờ những gì tôi muốn biết là, điều này thực sự nguy hiểm như thế nào? Nếu nó làm hỏng chương trình của tôi, nó không quá tệ. Mặt khác, nếu nó phá vỡ một cái gì đó bên ngoài chương trình của tôi, bởi vì bằng cách nào đó tôi đã quản lý để truy cập vào một số bộ nhớ hoàn toàn không liên quan, thì nó rất tệ, tôi tưởng tượng. Tôi đã đọc rất nhiều "bất cứ điều gì cũng có thể xảy ra", "phân khúc có thể là vấn đề tồi tệ nhất" , "đĩa cứng của bạn có thể chuyển sang màu hồng và kỳ lân có thể đang hát dưới cửa sổ của bạn", điều này thật tuyệt, nhưng điều gì thực sự nguy hiểm?

Những câu hỏi của tôi:

  1. Có thể đọc các giá trị từ cách bên ngoài mảng làm hỏng bất cứ điều gì ngoài chương trình của tôi không? Tôi sẽ tưởng tượng chỉ cần nhìn vào mọi thứ không thay đổi bất cứ điều gì, hoặc nó sẽ thay đổi thuộc tính 'lần cuối được mở' của một tập tin mà tôi tình cờ đạt được?
  2. Có thể thiết lập các giá trị bên ngoài mảng làm hỏng bất cứ thứ gì ngoài chương trình của tôi không? Từ câu hỏi Stack Overflow này, tôi tập hợp rằng có thể truy cập bất kỳ vị trí bộ nhớ nào, rằng không có đảm bảo an toàn.
  3. Bây giờ tôi chạy các chương trình nhỏ của mình từ bên trong XCode. Điều đó có cung cấp một số bảo vệ bổ sung xung quanh chương trình của tôi không, nơi nó không thể vươn ra ngoài bộ nhớ của chính nó? Nó có thể gây hại cho XCode không?
  4. Bất kỳ đề xuất về cách chạy mã lỗi vốn có của tôi một cách an toàn?

Tôi sử dụng OSX 10.7, Xcode 4.6.


Nói chung, HĐH sẽ bảo vệ chính nó và các quá trình khác khỏi sự cố của bạn. Tuy nhiên, đó không phải là thứ bạn nhất định muốn dựa vào.
Hot Licks

7
Ngoài ra, bạn sẽ không bao giờ "tình cờ tiếp cận" một tệp trên đĩa cứng của mình khi truy cập và lập chỉ mục mảng ngoài giới hạn (trong ram của bạn).
DrummerB

1
Tôi tin rằng bạn đang hỏi về mảng C, phải không? vì vậy điều đó không liên quan gì đến ObjC và không thực sự liên quan đến bất kỳ IDE nào.
Bryan Chen

17
Đây là ví dụ yêu thích của tôi về kết quả kỳ lạ (nó liên quan đến ngăn xếp, nhưng tôi thấy nó thực sự khai sáng ...).
phipsgabler

Câu trả lời:


125

Theo như tiêu chuẩn ISO C (định nghĩa chính thức của ngôn ngữ), việc truy cập vào một mảng bên ngoài giới hạn của nó có " hành vi không xác định ". Nghĩa đen của từ này là:

hành vi, khi sử dụng một cấu trúc chương trình không thể truy cập hoặc có lỗi hoặc dữ liệu sai, mà Tiêu chuẩn quốc tế này áp đặt không có yêu cầu

Một lưu ý không quy tắc mở rộng về điều này:

Hành vi không xác định có thể bao gồm từ bỏ qua hoàn toàn tình huống với kết quả không thể đoán trước, đến hành vi trong quá trình dịch hoặc thực hiện chương trình theo cách thức được ghi lại trong môi trường (có hoặc không có thông báo chẩn đoán), để chấm dứt dịch hoặc thực thi (với việc ban hành của một thông điệp chẩn đoán).

Đó là lý thuyết. Thực tế là gì?

Trong trường hợp "tốt nhất", bạn sẽ truy cập vào một phần bộ nhớ thuộc sở hữu của chương trình hiện đang chạy của bạn (điều này có thể khiến chương trình của bạn hoạt động sai), hoặc không thuộc sở hữu của chương trình đang chạy của bạn (có thể sẽ khiến chương trình của bạn bị lỗi sự cố với một cái gì đó giống như một lỗi phân khúc). Hoặc bạn có thể cố ghi vào bộ nhớ mà chương trình của bạn sở hữu, nhưng nó được đánh dấu chỉ đọc; điều này có thể cũng sẽ khiến chương trình của bạn bị sập

Đó là giả sử chương trình của bạn đang chạy trong một hệ điều hành cố gắng bảo vệ các tiến trình đang chạy đồng thời với nhau. Nếu mã của bạn đang chạy trên "kim loại trần", giả sử nếu đó là một phần của nhân hệ điều hành hoặc hệ thống nhúng, thì không có sự bảo vệ nào như vậy; mã sai của bạn là những gì được cho là cung cấp sự bảo vệ đó. Trong trường hợp đó, khả năng thiệt hại là lớn hơn đáng kể, bao gồm, trong một số trường hợp, thiệt hại vật lý đối với phần cứng (hoặc đối với những thứ hoặc người gần đó).

Ngay cả trong môi trường HĐH được bảo vệ, các biện pháp bảo vệ không phải lúc nào cũng 100%. Có những lỗi hệ điều hành cho phép các chương trình không có đặc quyền có được quyền truy cập root (hành chính), chẳng hạn. Ngay cả với các đặc quyền người dùng thông thường, một chương trình gặp trục trặc có thể tiêu tốn tài nguyên quá mức (CPU, bộ nhớ, đĩa), có thể làm giảm toàn bộ hệ thống. Rất nhiều phần mềm độc hại (vi rút, v.v.) khai thác lỗi tràn bộ đệm để có quyền truy cập trái phép vào hệ thống.

(Một ví dụ lịch sử: Tôi đã nghe nói rằng trên một số hệ thống cũ có bộ nhớ lõi , việc liên tục truy cập vào một vị trí bộ nhớ trong một vòng lặp chặt chẽ có thể khiến khối bộ nhớ đó bị tan chảy. Các khả năng khác bao gồm phá hủy màn hình CRT và di chuyển đọc / ghi đầu ổ đĩa với tần số hài hòa của tủ ổ đĩa, khiến nó đi ngang qua bàn và rơi xuống sàn.)

Và luôn có Skynet để lo lắng.

Điểm mấu chốt là đây: nếu bạn có thể viết một chương trình để làm điều gì đó xấu một cách có chủ ý , thì ít nhất về mặt lý thuyết là một chương trình lỗi có thể vô tình làm điều tương tự .

Trong thực tế, rất có thể chương trình lỗi của bạn chạy trên hệ thống MacOS X sẽ làm bất cứ điều gì nghiêm trọng hơn sự cố. Nhưng không thể ngăn chặn hoàn toàn mã lỗi làm những việc thực sự xấu.


1
cảm ơn, tôi thực sự hoàn toàn hiểu điều này Nhưng nó ngay lập tức kích hoạt một câu hỏi tiếp theo: một lập trình viên mới bắt đầu có thể làm gì, để bảo vệ máy tính của anh ta khỏi những sáng tạo khủng khiếp của chính anh ta / cô ta? Sau khi tôi đã thử nghiệm một chương trình kỹ lưỡng, tôi có thể giải phóng nó trên thế giới. Nhưng lần chạy thử đầu tiên chắc chắn là một chương trình không chính xác. Làm thế nào để các bạn giữ cho hệ thống của bạn an toàn từ chính bạn?
ChrisD

6
@ChrisD: Chúng ta có xu hướng may mắn. 8-)} Nghiêm túc, bảo vệ cấp hệ điều hành là khá tốt những ngày này. Trường hợp xấu nhất, nếu tôi viết một quả bom ngã ba tình cờ , tôi có thể phải khởi động lại để phục hồi. Nhưng thiệt hại thực sự đối với hệ thống có lẽ không đáng lo ngại, miễn là chương trình của bạn không cố làm điều gì đó nguy hiểm. Nếu bạn thực sự lo lắng, chạy chương trình trên máy ảo có thể không phải là ý tưởng tồi.
Keith Thompson

1
Mặt khác, tôi đã thấy rất nhiều điều kỳ lạ xảy ra trên các máy tính mà tôi đã sử dụng (các tệp bị hỏng, lỗi hệ thống không thể phục hồi, v.v.) và tôi không biết có bao nhiêu trong số chúng có thể đã được gây ra bởi một số chương trình C gây ra hành vi không xác định đáng sợ. (Cho đến nay không có con quỷ thực sự nào bay ra khỏi mũi tôi.)
Keith Thompson

1
cảm ơn vì đã dạy tôi ném bom ngã ba - Tôi đã thực hiện những thứ gần đó, khi cố gắng nắm bắt đệ quy :)
ChrisD

2
khoaamerican.com / article / từ vì vậy lửa vẫn có thể xảy ra với các thiết bị điện tử hiện đại.
Vịt Mooing

25

Nói chung, Hệ điều hành ngày nay (dù sao cũng phổ biến) chạy tất cả các ứng dụng trong vùng nhớ được bảo vệ bằng trình quản lý bộ nhớ ảo. Nó chỉ ra rằng việc đọc hoặc ghi vào một vị trí tồn tại trong không gian THỰC bên ngoài (các) khu vực đã được chỉ định / phân bổ cho quy trình của bạn không phải là quá khủng khiếp.

Câu trả lời trực tiếp:

1) Đọc sẽ gần như không bao giờ làm hỏng trực tiếp quá trình khác, tuy nhiên nó có thể gián tiếp làm hỏng quá trình nếu bạn tình cờ đọc một giá trị KEY được sử dụng để mã hóa, giải mã hoặc xác nhận chương trình / quy trình. Đọc ngoài giới hạn có thể có một số ảnh hưởng bất lợi / bất ngờ đối với mã của bạn nếu bạn đưa ra quyết định dựa trên dữ liệu bạn đang đọc

2) Cách duy nhất để bạn thực sự có thể DAMAGE một cái gì đó bằng cách ghi vào một chỗ trống có thể truy cập bằng địa chỉ bộ nhớ là nếu địa chỉ bộ nhớ mà bạn đang ghi thực sự là một thanh ghi phần cứng (một vị trí thực sự không phải để lưu trữ dữ liệu mà là để kiểm soát một phần nào đó của phần cứng) không phải là vị trí RAM. Trong thực tế, bạn vẫn sẽ không làm hỏng một cái gì đó bình thường trừ khi bạn đang viết một vị trí có thể lập trình một lần mà không thể ghi lại (hoặc một cái gì đó có tính chất đó).

3) Nói chung chạy từ bên trong trình gỡ lỗi chạy mã ở chế độ gỡ lỗi. Chạy trong chế độ gỡ lỗi TEND để (nhưng không phải luôn luôn) dừng mã của bạn nhanh hơn khi bạn đã thực hiện một cái gì đó được coi là không thực tế hoặc hoàn toàn bất hợp pháp.

4) Không bao giờ sử dụng macro, sử dụng cấu trúc dữ liệu đã có kiểm tra giới hạn chỉ số mảng được tích hợp, v.v ....

BỔ SUNG Tôi nên thêm rằng các thông tin trên thực sự chỉ dành cho các hệ thống sử dụng hệ điều hành có cửa sổ bảo vệ bộ nhớ. Nếu viết mã cho một hệ thống nhúng hoặc thậm chí một hệ thống sử dụng hệ điều hành (thời gian thực hoặc khác) không có cửa sổ bảo vệ bộ nhớ (hoặc cửa sổ địa chỉ ảo) thì người ta nên thận trọng hơn trong việc đọc và ghi vào bộ nhớ. Ngoài ra trong những trường hợp này, các biện pháp mã hóa AN TOÀN và AN TOÀN phải luôn được sử dụng để tránh các vấn đề bảo mật.


4
Thực hành mã hóa an toàn và bảo mật nên luôn luôn được sử dụng.
Nik Bougalis

3
Tôi sẽ đề nghị KHÔNG sử dụng thử / bắt cho mã lỗi trừ khi bạn bắt được các ngoại lệ rất cụ thể và biết cách phục hồi từ chúng. Catch (...) là điều tồi tệ nhất bạn có thể thêm vào mã lỗi.
Eugene

1
@NikBougalis - Tôi hoàn toàn đồng ý, nhưng NGAY CẢ QUAN TRỌNG HƠN NỮA nếu HĐH không bao gồm bảo vệ bộ nhớ / không gian địa chỉ ảo hoặc thiếu HĐH :-)
kèn thổi vào

@Eugene - Tôi chưa bao giờ nhận thấy rằng đó là một vấn đề đối với tôi, nhưng tôi đồng ý với bạn, tôi đã chỉnh sửa nó chưa :-)
kèn trumpet 26/03/13

1) bạn có nghĩa là thiệt hại bởi vì tôi sẽ tiết lộ một cái gì đó nên được giữ bí mật? 2) Tôi không chắc là tôi hiểu ý bạn, nhưng tôi đoán tôi chỉ truy cập RAM khi cố gắng truy cập các vị trí bên ngoài giới hạn mảng?
ChrisD

9

Không kiểm tra giới hạn có thể dẫn đến các tác dụng phụ xấu xí, bao gồm các lỗ hổng bảo mật. Một trong những cái xấu nhất là thực thi mã tùy ý . Trong ví dụ cổ điển: nếu bạn có một mảng kích thước cố định và sử dụng strcpy()để đặt một chuỗi do người dùng cung cấp ở đó, người dùng có thể cung cấp cho bạn một chuỗi tràn bộ đệm và ghi đè lên các vị trí bộ nhớ khác, bao gồm cả địa chỉ mã nơi CPU sẽ trả về khi hàm của bạn kết thúc

Điều đó có nghĩa là người dùng của bạn có thể gửi cho bạn một chuỗi sẽ khiến chương trình của bạn thực sự gọi exec("/bin/sh"), nó sẽ biến nó thành shell, thực thi bất cứ thứ gì anh ta muốn trên hệ thống của bạn, bao gồm thu thập tất cả dữ liệu của bạn và biến máy của bạn thành nút botnet.

Xem Smashing The Stack For Fun And Profit để biết chi tiết về cách thực hiện việc này.


Tôi biết rằng tôi không nên truy cập các phần tử mảng ngoài giới hạn, cảm ơn vì đã củng cố điểm đó. Nhưng câu hỏi là, ngoài việc gây ra tất cả các tác hại cho chương trình của tôi, tôi có thể vô tình vươn ra ngoài bộ nhớ của chương trình không? Và ý tôi là trên OSX.
ChrisD

@ChrisD: OS X là một hệ điều hành hiện đại, vì vậy nó sẽ cung cấp cho bạn bảo vệ bộ nhớ đầy đủ. Ví dụ, bạn không nên giới hạn những gì chương trình của bạn được phép làm. Điều này không bao gồm gây rối với các quy trình khác (trừ khi bạn đang chạy dưới quyền root).
che

Tôi muốn nói rằng dưới đặc quyền vòng 0, không phải quyền root.
Ruslan

Thú vị hơn là các trình biên dịch siêu hiện đại có thể quyết định rằng nếu mã cố đọc foo[0]qua foo[len-1]sau khi trước đó đã sử dụng kiểm tra lenđộ dài mảng để thực thi hoặc bỏ qua một đoạn mã, trình biên dịch sẽ thoải mái chạy mã khác một cách vô điều kiện nếu ứng dụng sở hữu bộ lưu trữ vượt quá mảng và các hiệu ứng của việc đọc nó sẽ là lành tính, nhưng hiệu ứng của việc gọi mã khác sẽ không được.
supercat

8

Bạn viết:

Tôi đã đọc rất nhiều "bất cứ điều gì cũng có thể xảy ra", "phân khúc có thể là vấn đề tồi tệ nhất", "ổ cứng của bạn có thể chuyển sang màu hồng và kỳ lân có thể đang hát dưới cửa sổ của bạn", điều này thật tuyệt, nhưng điều gì thực sự nguy hiểm?

Hãy đặt nó theo cách đó: tải một khẩu súng. Chỉ nó bên ngoài cửa sổ mà không có bất kỳ mục tiêu cụ thể và lửa. Nguy hiểm là gì?

Vấn đề là bạn không biết. Nếu mã của bạn ghi đè lên một cái gì đó làm hỏng chương trình của bạn, bạn sẽ ổn vì nó sẽ dừng nó ở trạng thái xác định. Tuy nhiên, nếu nó không sụp đổ thì các vấn đề bắt đầu phát sinh. Những tài nguyên nào nằm dưới sự kiểm soát của chương trình của bạn và nó có thể làm gì với chúng? Những tài nguyên nào có thể kiểm soát chương trình của bạn và nó có thể làm gì với chúng? Tôi biết ít nhất một vấn đề lớn đã được gây ra bởi một tràn như vậy. Vấn đề là ở một chức năng thống kê dường như vô nghĩa đã làm rối tung một số bảng chuyển đổi không liên quan cho cơ sở dữ liệu sản xuất. Kết quả là một số dọn dẹp rất tốn kém sau đó. Trên thực tế, nó sẽ rẻ hơn và dễ xử lý hơn nếu vấn đề này đã định dạng các đĩa cứng ... với các từ khác: kỳ lân màu hồng có thể là vấn đề nhỏ nhất của bạn.

Ý tưởng rằng hệ điều hành của bạn sẽ bảo vệ bạn là lạc quan. Nếu có thể hãy cố gắng tránh viết ra khỏi giới hạn.


ok, đây chính xác là những gì tôi sợ. Tôi sẽ 'cố gắng tránh viết ra khỏi giới hạn', nhưng, nhìn thấy những gì tôi đã làm trong vài tháng qua, tôi chắc chắn sẽ làm điều đó rất nhiều. Làm thế nào mà các bạn có được lập trình tốt như vậy mà không có cách thực hành an toàn?
ChrisD

3
Ai nói rằng bất cứ điều gì đã từng an toàn;)
Udo Klein

7

Không chạy chương trình của bạn dưới quyền root hoặc bất kỳ người dùng đặc quyền nào khác sẽ không gây hại cho bất kỳ hệ thống nào của bạn, vì vậy nói chung đây có thể là một ý tưởng tốt.

Bằng cách ghi dữ liệu vào một số vị trí bộ nhớ ngẫu nhiên, bạn sẽ không trực tiếp "làm hỏng" bất kỳ chương trình nào khác đang chạy trên máy tính của mình khi mỗi tiến trình chạy trong không gian bộ nhớ của chính nó.

Nếu bạn cố gắng truy cập bất kỳ bộ nhớ nào không được phân bổ cho quy trình của mình, hệ điều hành sẽ ngăn chương trình của bạn thực thi với lỗi phân đoạn.

Vì vậy, trực tiếp (không chạy bằng root và truy cập trực tiếp vào các tệp như / dev / mem), không có gì nguy hiểm khi chương trình của bạn sẽ can thiệp vào bất kỳ chương trình nào khác đang chạy trên hệ điều hành của bạn.

Tuy nhiên - và có lẽ đây là những gì bạn đã nghe về mức độ nguy hiểm - bằng cách viết một cách mù quáng dữ liệu ngẫu nhiên vào các vị trí bộ nhớ ngẫu nhiên, bạn chắc chắn có thể làm hỏng bất cứ thứ gì bạn có thể làm hỏng.

Ví dụ, chương trình của bạn có thể muốn xóa một tệp cụ thể được cung cấp bởi một tên tệp được lưu trữ ở đâu đó trong chương trình của bạn. Nếu tình cờ, bạn chỉ cần ghi đè lên vị trí lưu trữ tên tệp, bạn có thể xóa một tệp rất khác thay thế.


1
Tuy nhiên, nếu bạn đang chạy với quyền root (hoặc một số người dùng đặc quyền khác), hãy coi chừng. Bộ đệm và mảng tràn là một khai thác phần mềm độc hại phổ biến.
John Bode

thực ra tài khoản tôi sử dụng cho tất cả các máy tính hàng ngày của tôi không phải là tài khoản quản trị viên (tôi sử dụng thuật ngữ OSX vì đó là hệ thống của tôi). Bạn có nghĩa là nói với tôi rằng tôi không thể làm hỏng thứ gì đó bằng cách cố gắng đặt BẤT K location vị trí bộ nhớ nào? Đó thực sự là một tin tuyệt vời!
ChrisD

Như đã đề cập trước tác hại tồi tệ nhất bạn có thể gây ra do tai nạn là tác hại tồi tệ nhất bạn có thể làm với tư cách là người dùng. Nếu bạn muốn chắc chắn 100% không phá hủy bất kỳ dữ liệu nào của bạn, có lẽ bạn có thể muốn thêm tài khoản khác vào máy tính của mình và thử nghiệm điều đó.
mikyra

1
@mikyra: Điều đó chỉ đúng nếu các cơ chế bảo vệ của hệ thống có hiệu quả 100%. Sự tồn tại của phần mềm độc hại cho thấy bạn không thể luôn dựa vào đó. (Tôi không muốn đề xuất rằng điều đó thực sự đáng lo ngại; có thể, nhưng không chắc là chương trình có thể vô tình khai thác các lỗ hổng bảo mật tương tự được khai thác bởi phần mềm độc hại.)
Keith Thompson

1
Danh sách ở đây bao gồm: Chạy mã từ các nguồn không đáng tin cậy. Chỉ cần nhấp vào nút OK trên bất kỳ cửa sổ bật lên nào của tường lửa mà không cần đọc hoặc tắt hoàn toàn nếu không thể thực hiện kết nối mạng mong muốn. Vá các tệp nhị phân với bản hack mới nhất từ ​​các nguồn đáng ngờ. Đó không phải là lỗi của kho tiền nếu chủ sở hữu sẽ tự nguyện mời bất kỳ tên trộm nào bằng cả hai cánh tay và cánh cửa kiên cố cực kỳ rộng mở.
mikyra

4

NSArrays trong Objective-C được gán một khối bộ nhớ cụ thể. Vượt quá giới hạn của mảng có nghĩa là bạn sẽ truy cập vào bộ nhớ không được gán cho mảng. Điều này có nghĩa là:

  1. Bộ nhớ này có thể có bất kỳ giá trị. Không có cách nào để biết liệu dữ liệu có hợp lệ hay không dựa trên loại dữ liệu của bạn.
  2. Bộ nhớ này có thể chứa thông tin nhạy cảm như khóa riêng hoặc thông tin đăng nhập của người dùng khác.
  3. Địa chỉ bộ nhớ có thể không hợp lệ hoặc được bảo vệ.
  4. Bộ nhớ có thể có giá trị thay đổi vì nó được truy cập bởi một chương trình hoặc luồng khác.
  5. Những thứ khác sử dụng không gian địa chỉ bộ nhớ, chẳng hạn như các cổng được ánh xạ bộ nhớ.
  6. Ghi dữ liệu vào địa chỉ bộ nhớ không xác định có thể làm hỏng chương trình của bạn, ghi đè lên không gian bộ nhớ hệ điều hành và thường khiến mặt trời nổ tung.

Từ khía cạnh của chương trình của bạn, bạn luôn muốn biết khi nào mã của bạn vượt quá giới hạn của một mảng. Điều này có thể dẫn đến các giá trị không xác định được trả về, khiến ứng dụng của bạn bị sập hoặc cung cấp dữ liệu không hợp lệ.


NSArrayscó ngoại lệ giới hạn. Và câu hỏi này dường như là về mảng C.
DrummerB

Tôi thực sự có nghĩa là mảng C. Tôi biết có NSArray, nhưng hiện tại hầu hết các bài tập của tôi đều ở C
ChrisD

4

Bạn có thể muốn thử sử dụng memcheckcông cụ trong Valgrind khi bạn kiểm tra mã của mình - nó sẽ không bắt gặp các vi phạm giới hạn mảng riêng lẻ trong khung ngăn xếp, nhưng nó sẽ gây ra nhiều vấn đề về bộ nhớ khác, bao gồm cả những vấn đề gây ra sự phức tạp, rộng hơn các vấn đề ngoài phạm vi của một chức năng duy nhất.

Từ hướng dẫn:

Memcheck là một bộ phát hiện lỗi bộ nhớ. Nó có thể phát hiện các vấn đề phổ biến sau đây trong các chương trình C và C ++.

  • Truy cập bộ nhớ mà bạn không nên, ví dụ như các khối heap quá mức và kém hiệu quả, vượt quá đỉnh của ngăn xếp và truy cập bộ nhớ sau khi nó được giải phóng.
  • Sử dụng các giá trị không xác định, nghĩa là các giá trị chưa được khởi tạo hoặc được lấy từ các giá trị không xác định khác.
  • Giải phóng bộ nhớ heap không chính xác, chẳng hạn như các khối heap giải phóng kép hoặc sử dụng không đúng cách của malloc / new / new [] so với free / xóa / xóa []
  • Chồng chéo src và dst con trỏ trong memcpy và các hàm liên quan.
  • Rò rỉ bộ nhớ.

ETA: Mặc dù, như câu trả lời của Kaz nói, đó không phải là thuốc chữa bách bệnh và không phải lúc nào cũng cho đầu ra hữu ích nhất, đặc biệt là khi bạn đang sử dụng các mẫu truy cập thú vị .


Tôi nghi ngờ Người phân tích XCode sẽ tìm thấy phần lớn điều đó? và câu hỏi của tôi không phải là làm thế nào để tìm ra những lỗi này, nhưng nếu thực hiện một chương trình vẫn còn những lỗi này thì nguy hiểm cho bộ nhớ không được phân bổ cho chương trình của tôi. Tôi sẽ phải thực hiện chương trình để xem các lỗi xảy ra
ChrisD

3

Nếu bạn từng lập trình cấp hệ thống hoặc lập trình hệ thống nhúng, những điều rất tệ có thể xảy ra nếu bạn ghi vào các vị trí bộ nhớ ngẫu nhiên. Các hệ thống cũ và nhiều bộ điều khiển vi mô sử dụng IO được ánh xạ bộ nhớ, do đó, ghi vào vị trí bộ nhớ ánh xạ tới thanh ghi ngoại vi có thể tàn phá, đặc biệt là nếu nó được thực hiện không đồng bộ.

Một ví dụ là lập trình bộ nhớ flash. Chế độ lập trình trên các chip bộ nhớ được kích hoạt bằng cách viết một chuỗi các giá trị cụ thể đến các vị trí cụ thể bên trong phạm vi địa chỉ của chip. Nếu một quá trình khác được ghi vào bất kỳ vị trí nào khác trong chip trong khi điều đó đang diễn ra, nó sẽ khiến chu trình lập trình thất bại.

Trong một số trường hợp, phần cứng sẽ bao bọc các địa chỉ xung quanh (hầu hết các bit / byte quan trọng của địa chỉ bị bỏ qua), do đó, việc ghi vào một địa chỉ nằm ngoài phần cuối của không gian địa chỉ vật lý sẽ thực sự dẫn đến việc dữ liệu được ghi ngay giữa mọi thứ.

Và cuối cùng, các CPU cũ hơn như MC68000 có thể bị khóa đến mức chỉ có thiết lập lại phần cứng mới có thể khiến chúng hoạt động trở lại. Tôi đã không làm việc với họ trong một vài thập kỷ nhưng tôi tin rằng khi gặp phải lỗi bus (bộ nhớ không tồn tại) trong khi cố gắng xử lý một ngoại lệ, nó sẽ chỉ dừng lại cho đến khi xác nhận lại phần cứng.

Đề xuất lớn nhất của tôi là một đầu cắm trắng trợn cho một sản phẩm, nhưng tôi không có hứng thú cá nhân với nó và tôi không liên kết với chúng theo bất kỳ cách nào - nhưng dựa trên một vài thập kỷ lập trình C và các hệ thống nhúng trong đó độ tin cậy là rất quan trọng, PC của Gimpel Lint sẽ không chỉ phát hiện ra các loại lỗi đó, nó sẽ làm cho một lập trình viên C / C ++ tốt hơn khỏi bạn bằng cách liên tục làm phiền bạn về những thói quen xấu.

Tôi cũng khuyên bạn nên đọc tiêu chuẩn mã hóa MISRA C, nếu bạn có thể lấy một bản sao từ ai đó. Tôi đã không thấy bất kỳ cái nào gần đây nhưng trong những ngày xưa họ đã đưa ra một lời giải thích tốt về lý do tại sao bạn nên / không nên làm những điều họ bao quát.

Nói về bạn, nhưng khoảng lần thứ 2 hoặc thứ 3 tôi nhận được một bản trích dẫn hoặc treo máy từ bất kỳ ứng dụng nào, ý kiến ​​của tôi về bất kỳ công ty nào sản xuất nó đều giảm một nửa. Lần thứ 4 hoặc thứ 5 và bất cứ thứ gì gói trở thành kệ và tôi lái một cọc gỗ qua trung tâm của gói / đĩa nó đến để đảm bảo nó không bao giờ quay trở lại ám ảnh tôi.


Tùy thuộc vào hệ thống, các lần đọc ngoài phạm vi cũng có thể kích hoạt hành vi không thể đoán trước hoặc chúng có thể là lành tính, mặc dù hành vi phần cứng lành tính đối với các tải ngoài phạm vi không ngụ ý hành vi biên dịch lành tính.
supercat

2

Tôi đang làm việc với một trình biên dịch cho một chip DSP, nó cố tình tạo mã truy cập vào một phần cuối của một mảng trong mã C mà không!

Điều này là do các vòng lặp được cấu trúc sao cho phần cuối của một lần lặp tìm nạp trước một số dữ liệu cho lần lặp tiếp theo. Vì vậy, dữ liệu được tìm nạp trước ở cuối lần lặp cuối cùng không bao giờ thực sự được sử dụng.

Viết mã C như thế gọi ra hành vi không xác định, nhưng đó chỉ là một hình thức từ một tài liệu tiêu chuẩn liên quan đến tính di động tối đa.

Thường xuyên hơn là không, một chương trình truy cập ngoài giới hạn không được tối ưu hóa một cách khéo léo. Nó chỉ đơn giản là lỗi. Mã tìm nạp một số giá trị rác và, không giống như các vòng lặp được tối ưu hóa của trình biên dịch đã nói ở trên, mã sau đó sử dụng giá trị trong các tính toán tiếp theo, do đó làm hỏng theim.

Rất đáng để bắt lỗi như vậy, và do đó, đáng để làm cho hành vi không được xác định chỉ vì lý do đó: để thời gian chạy có thể tạo ra một thông báo chẩn đoán như "mảng tràn vào dòng 42 của main.c".

Trên các hệ thống có bộ nhớ ảo, một mảng có thể được phân bổ sao cho địa chỉ tiếp theo nằm trong vùng không được ánh xạ của bộ nhớ ảo. Truy cập sau đó sẽ đánh bom chương trình.

Bên cạnh đó, lưu ý rằng trong C, chúng ta được phép tạo một con trỏ đi qua cuối mảng. Và con trỏ này phải so sánh lớn hơn bất kỳ con trỏ nào với phần bên trong của một mảng. Điều này có nghĩa là việc triển khai C không thể đặt một mảng ngay ở cuối bộ nhớ, trong đó một địa chỉ cộng sẽ bao quanh và trông nhỏ hơn các địa chỉ khác trong mảng.

Tuy nhiên, truy cập vào các giá trị chưa được khởi tạo hoặc ngoài giới hạn đôi khi là một kỹ thuật tối ưu hóa hợp lệ, ngay cả khi không thể mang theo tối đa. Đây là ví dụ tại sao công cụ Valgrind không báo cáo quyền truy cập vào dữ liệu chưa được khởi tạo khi những truy cập đó xảy ra, nhưng chỉ khi giá trị này được sử dụng theo cách nào đó có thể ảnh hưởng đến kết quả của chương trình. Bạn nhận được một chẩn đoán như "nhánh có điều kiện trong xxx: nnn phụ thuộc vào giá trị chưa được khởi tạo" và đôi khi có thể khó theo dõi nơi nó bắt nguồn. Nếu tất cả các truy cập như vậy bị mắc kẹt ngay lập tức, sẽ có rất nhiều tích cực sai phát sinh từ mã được tối ưu hóa trình biên dịch cũng như mã được tối ưu hóa chính xác bằng tay.

Nói về điều này, tôi đã làm việc với một số codec từ một nhà cung cấp đã đưa ra các lỗi này khi được chuyển sang Linux và chạy dưới Valgrind. Nhưng nhà cung cấp đã thuyết phục tôi rằng chỉ có một số bitvề giá trị được sử dụng thực sự đến từ bộ nhớ chưa được khởi tạo và các bit đó được logic cẩn thận tránh .. Chỉ có các bit tốt của giá trị được sử dụng và Valgrind không có khả năng theo dõi từng bit riêng lẻ. Tài liệu chưa được khởi tạo đến từ việc đọc một từ qua cuối luồng dữ liệu được mã hóa, nhưng mã biết có bao nhiêu bit trong luồng và sẽ không sử dụng nhiều bit hơn thực tế. Vì việc truy cập vượt quá cuối mảng luồng bit không gây ra bất kỳ tác hại nào cho kiến ​​trúc DSP (không có bộ nhớ ảo sau mảng, không có cổng ánh xạ bộ nhớ và địa chỉ không bao bọc) nên đây là một kỹ thuật tối ưu hóa hợp lệ.

"Hành vi không xác định" không thực sự có ý nghĩa nhiều, vì theo ISO C, đơn giản chỉ bao gồm một tiêu đề không được xác định trong tiêu chuẩn C hoặc gọi một hàm không được xác định trong chính chương trình hoặc tiêu chuẩn C, là các ví dụ về không xác định hành vi. Hành vi không xác định không có nghĩa là "không được xác định bởi bất kỳ ai trên hành tinh" chỉ "không được xác định theo tiêu chuẩn ISO C". Nhưng tất nhiên, đôi khi hành vi không xác định thực sự hoàn toàn không được xác định bởi bất cứ ai.


Ngoài ra, với điều kiện là tồn tại ít nhất một chương trình mà một quá trình thực hiện cụ thể xử lý chính xác mặc dù nó đánh thuế tất cả các giới hạn thực hiện được đưa ra trong Tiêu chuẩn, việc triển khai đó có thể hành xử tùy tiện khi được cung cấp bất kỳ chương trình nào khác không có vi phạm ràng buộc và vẫn bị " tuân thủ ". Do đó, 99,999% chương trình C (bất cứ điều gì khác ngoài "một chương trình" của nền tảng) phụ thuộc vào các hành vi trong đó Tiêu chuẩn áp đặt không có yêu cầu.
supercat

1

Ngoài chương trình của riêng bạn, tôi không nghĩ bạn sẽ phá vỡ bất cứ điều gì, trong trường hợp xấu nhất bạn sẽ cố đọc hoặc ghi từ một địa chỉ bộ nhớ tương ứng với một trang mà kernel không gán cho các quy tắc của bạn, tạo ra ngoại lệ phù hợp và bị giết (ý tôi là, quá trình của bạn).


3
..Gì? Làm thế nào về việc ghi đè bộ nhớ trong quy trình của riêng bạn được sử dụng để lưu trữ một số biến được sử dụng sau này ... hiện đã thay đổi một cách bí ẩn giá trị của nó! Những lỗi đó là vô số niềm vui để theo dõi, tôi đảm bảo với bạn. Một segfault sẽ là kết quả tốt nhất . -1
Ed S.

2
Ý tôi là anh ấy sẽ không "phá vỡ" các quy trình khác, ngoài chương trình của chính anh ấy;)
jbss

Tôi thực sự không quan tâm nếu tôi phá vỡ chương trình của riêng tôi. Tôi chỉ đang học, chương trình rõ ràng là sai dù sao nếu tôi truy cập bất cứ thứ gì ngoài ràng buộc của mảng của tôi. Tôi càng ngày càng lo lắng về những rủi ro khi phá vỡ thứ khác trong khi gỡ lỗi những sáng tạo của mình
ChrisD 26/03/13

Vấn đề là: tôi có thể chắc chắn nếu tôi cố truy cập vào bộ nhớ không được chỉ định cho tôi, rằng quá trình của tôi sẽ bị giết? (đang ở trên OSX)
ChrisD

3
Nhiều năm trước, tôi từng là một lập trình viên C vụng về. Tôi đã truy cập các mảng bên ngoài giới hạn của họ hàng trăm lần. Ngoài quá trình của tôi bị giết bởi hệ điều hành, không có gì xảy ra.
jbss
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.