Nếu một số quá lớn, nó có tràn sang vị trí bộ nhớ tiếp theo không?


30

Tôi đã xem xét lập trình C và chỉ có một vài điều làm phiền tôi.

Hãy lấy mã này làm ví dụ:

int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
    printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);

Tôi biết rằng một int có thể giữ giá trị tối đa là 2.147.483.647. Vì vậy, bằng cách vượt qua điều đó, nó có "tràn" sang địa chỉ bộ nhớ tiếp theo khiến phần tử 2 xuất hiện dưới dạng "-2147483648" tại địa chỉ đó không? Nhưng điều đó không thực sự có ý nghĩa bởi vì trong đầu ra, nó vẫn nói rằng địa chỉ tiếp theo giữ giá trị 4, sau đó 5. Nếu số đó đã tràn sang địa chỉ tiếp theo thì điều đó sẽ không thay đổi giá trị được lưu trữ tại địa chỉ đó ?

Tôi mơ hồ nhớ từ lập trình trong MIPS hội và xem các địa chỉ thay đổi giá trị trong chương trình từng bước rằng các giá trị được gán cho các địa chỉ đó sẽ thay đổi.

Trừ khi tôi nhớ không chính xác thì đây là một câu hỏi khác: Nếu số được gán cho một địa chỉ cụ thể lớn hơn loại (như trong myArray [2]) thì nó có ảnh hưởng đến các giá trị được lưu trữ ở địa chỉ tiếp theo không?

Ví dụ: Chúng tôi có int myNum = 4 tỷ tại địa chỉ 0x10010000. Tất nhiên myNum không thể lưu trữ 4 tỷ vì vậy nó xuất hiện dưới dạng một số âm tại địa chỉ đó. Mặc dù không thể lưu trữ số lượng lớn này, nhưng nó không ảnh hưởng đến giá trị được lưu trữ tại địa chỉ tiếp theo là 0x10010004. Chính xác?

Các địa chỉ bộ nhớ chỉ có đủ không gian để chứa các kích thước số / ký tự nhất định và nếu kích thước vượt quá giới hạn thì nó sẽ được thể hiện khác nhau (như cố lưu 4 tỷ vào int nhưng nó sẽ xuất hiện dưới dạng số âm) và do đó, nó không có hiệu lực đối với các số / ký tự được lưu trữ tại địa chỉ tiếp theo.

Xin lỗi nếu tôi quá nhiệt tình Tôi đã có một bộ não lớn đánh rắm cả ngày từ điều này.


10
Bạn có thể bị nhầm lẫn với tràn chuỗi .
Robbie Dee

19
Bài tập về nhà: Sửa một CPU đơn giản để nó không tràn. Bạn sẽ thấy rằng logic trở nên phức tạp hơn nhiều, tất cả chỉ vì một "tính năng" sẽ đảm bảo các lỗ hổng bảo mật ở mọi nơi mà không hữu ích ngay từ đầu.
phihag

4
Nếu bạn cần số thực sự lớn, có thể có một đại diện số làm tăng số lượng bộ nhớ mà nó sử dụng để phù hợp với số lớn. Bản thân bộ xử lý không thể làm điều này và nó không phải là một tính năng của ngôn ngữ C, nhưng một thư viện có thể thực hiện nó - một thư viện C phổ biến là thư viện số học GNU Precision Precision . Thư viện phải quản lý bộ nhớ để lưu trữ các số có chi phí hiệu năng trên đầu số học. Rất nhiều ngôn ngữ có loại điều này được tích hợp (không tránh được chi phí).
Steve314

1
viết một bài kiểm tra đơn giản, tôi không phải là lập trình viên C mà là một người theo dõi int c = INT.MAXINT; c+=1;và xem điều gì đã xảy ra với c.
JonH

2
@JonH: Vấn đề là tràn trong Hành vi không xác định. Trình biên dịch AC có thể phát hiện ra mã đó và suy ra rằng đó là mã không thể truy cập được nó tràn vô điều kiện. Vì mã không thể truy cập không quan trọng, nó có thể được loại bỏ. Kết quả cuối cùng: không còn mã.
MSalters

Câu trả lời:


48

Không nó không. Trong C, các biến có một bộ địa chỉ bộ nhớ cố định để làm việc. Nếu bạn đang làm việc trên một hệ thống có 4 byte intsvà bạn đặt một intbiến thành 2,147,483,647rồi thêm 1, biến thường sẽ chứa -2147483648. (Trên hầu hết các hệ thống. Hành vi thực sự không được xác định.) Không có vị trí bộ nhớ nào khác sẽ được sửa đổi.

Về bản chất, trình biên dịch sẽ không cho phép bạn gán một giá trị quá lớn cho loại. Điều này sẽ tạo ra một lỗi biên dịch. Nếu bạn buộc nó với một trường hợp, giá trị sẽ bị cắt ngắn.

Nhìn theo cách bit, nếu loại chỉ có thể lưu trữ 8 bit và bạn cố gắng ép giá trị 1010101010101vào nó bằng một trường hợp, bạn sẽ kết thúc với 8 bit dưới cùng, hoặc 01010101.

Trong ví dụ của bạn, bất kể bạn làm gì myArray[2], myArray[3]sẽ chứa '4'. Không có "tràn". Bạn đang cố gắng đặt một cái gì đó lớn hơn 4 byte, nó sẽ chỉ tắt mọi thứ ở mức cao, để lại 4 byte dưới cùng. Trên hầu hết các hệ thống, điều này sẽ dẫn đến -2147483648.

Từ quan điểm thực tế, bạn muốn chắc chắn rằng điều này sẽ không bao giờ xảy ra. Những loại tràn này thường dẫn đến các khuyết tật khó giải quyết. Nói cách khác, nếu bạn nghĩ rằng có bất kỳ cơ hội nào thì tất cả các giá trị của bạn sẽ ở mức tiền tỷ, đừng sử dụng int.


52
Nếu bạn đang làm việc trên một hệ thống có int 4 byte và bạn đặt biến int thành 2.147.483.647 và sau đó thêm 1, biến sẽ chứa -2147483648. => Không , đó là Hành vi không xác định , do đó, nó có thể lặp lại hoặc nó có thể làm điều gì đó hoàn toàn khác; Tôi đã thấy các trình biên dịch tối ưu hóa kiểm tra dựa trên việc không có tràn và có các vòng lặp vô hạn chẳng hạn ...
Matthieu M.

Xin lỗi, vâng, bạn đúng. Tôi nên đã thêm một "thường" trong đó.
Gort Robot

@MatthieuM từ góc độ ngôn ngữ , đó là sự thật. Về mặt thực thi trên một hệ thống nhất định, đó là những gì chúng ta đang nói ở đây, nó hoàn toàn vô nghĩa.
hobbs

@hobbs: Vấn đề là khi trình biên dịch xử lý chương trình vì Hành vi không xác định, thực sự chạy chương trình sẽ thực sự tạo ra một hành vi không mong muốn, có thể so sánh với hiệu ứng ghi đè.
Matthieu M.

24

Ký tràn số nguyên là hành vi không xác định. Nếu điều này xảy ra chương trình của bạn không hợp lệ. Trình biên dịch không bắt buộc phải kiểm tra điều này cho bạn, vì vậy nó có thể tạo ra một tệp thực thi có vẻ như để làm một cái gì đó hợp lý, nhưng không có gì đảm bảo rằng nó sẽ làm được.

Tuy nhiên, tràn số nguyên không dấu được xác định rõ. Nó sẽ bọc modulo UINT_MAX + 1. Bộ nhớ không bị chiếm bởi biến của bạn sẽ không bị ảnh hưởng.

Xem thêm https://stackoverflow.com/q/18195715/951890


tràn số nguyên đã ký cũng được xác định rõ là tràn số nguyên không dấu. nếu từ có bit $ N $, ranh giới trên của tràn số nguyên đã ký là $$ 2 ^ {N-1} -1 $$ (trong đó nó bao quanh đến $ -2 ^ {N-1} $) trong khi ranh giới trên cho tràn số nguyên không dấu là $$ 2 ^ N - 1 $$ (trong đó nó bao quanh đến $ 0 $). cùng các cơ chế cộng và trừ, cùng kích thước của phạm vi số ($ 2 ^ N $) có thể được biểu diễn. chỉ là một ranh giới khác nhau của tràn.
robert bristow-johnson

1
@ robertbristow-johnson: Không theo tiêu chuẩn C.
Vaughn Cato

tốt, tiêu chuẩn đôi khi là lỗi thời. Nhìn vào tài liệu tham khảo SO, có một nhận xét đánh thẳng vào nó: "Tuy nhiên, điều lưu ý quan trọng ở đây là không có kiến ​​trúc nào trong thế giới hiện đại sử dụng bất cứ thứ gì ngoài số học bổ sung có chữ ký của 2. Điều đó các tiêu chuẩn ngôn ngữ vẫn cho phép thực hiện ví dụ: PDP-1 là một cổ vật lịch sử thuần túy. - Andy Ross ngày 12 tháng 8 năm 13 lúc 20:12 "
robert bristow-johnson

Tôi cho rằng nó không nằm trong tiêu chuẩn C, nhưng tôi cho rằng có thể có một triển khai trong đó số học nhị phân thông thường không được sử dụng cho int. Tôi cho rằng họ có thể sử dụng mã Gray hoặc BCD hoặc EBCDIC . dunno tại sao mọi người sẽ thiết kế phần cứng để làm số học với mã Gray hoặc EBCDIC, nhưng một lần nữa, tôi không biết tại sao mọi người sẽ làm unsignedvới nhị phân và ký kết intvới bất cứ điều gì ngoài phần bù 2.
robert bristow-johnson

14

Vì vậy, có hai điều ở đây:

  • cấp độ ngôn ngữ: ngữ nghĩa của C là gì
  • cấp độ máy: ngữ nghĩa của lắp ráp / CPU bạn sử dụng là gì

Ở cấp độ ngôn ngữ:

Trong C:

  • tràn và tràn được định nghĩa là số học modulo cho số nguyên không dấu , do đó giá trị "vòng lặp" của chúng
  • tràn và tràn là các hành vi không xác định cho các số nguyên đã ký , do đó, bất cứ điều gì cũng có thể xảy ra

Đối với những người muốn có một ví dụ "bất cứ điều gì", tôi đã thấy:

for (int i = 0; i >= 0; i++) {
    ...
}

trở thành:

for (int i = 0; true; i++) {
    ...
}

và vâng, đây là một sự chuyển đổi hợp pháp.

Điều đó có nghĩa là thực sự có những rủi ro tiềm ẩn của việc ghi đè bộ nhớ khi tràn do một số chuyển đổi trình biên dịch kỳ lạ.

Lưu ý: trên Clang hoặc gcc, sử dụng -fsanitize=undefinedtrong Debug để kích hoạt Bộ khử trùng hành vi không xác định sẽ hủy bỏ dòng chảy tràn / tràn số nguyên đã ký.

Hoặc nó có nghĩa là bạn có thể ghi đè lên bộ nhớ bằng cách sử dụng kết quả của hoạt động để lập chỉ mục (không được kiểm tra) thành một mảng. Thật không may, điều này rất có thể xảy ra trong trường hợp không phát hiện tràn / tràn.

Lưu ý: trên Clang hoặc gcc, sử dụng -fsanitize=addresstrong Debug để kích hoạt Trình khử trùng địa chỉ sẽ hủy bỏ quyền truy cập ngoài giới hạn.


Ở cấp độ máy :

Nó thực sự phụ thuộc vào hướng dẫn lắp ráp và CPU bạn sử dụng:

  • trên x86, ADD sẽ sử dụng 2 bổ sung cho tràn / tràn và đặt OF (Cờ tràn)
  • trên CPU Mill tương lai, sẽ có 4 chế độ tràn khác nhau cho Add:
    • Modulo: Modulo 2 bổ sung
    • Bẫy: một cái bẫy được tạo ra, tạm dừng tính toán
    • Bão hòa: giá trị bị kẹt ở mức tối thiểu khi tràn hoặc tối đa khi tràn
    • Double Width: kết quả được tạo trong thanh ghi hai chiều rộng

Lưu ý rằng cho dù mọi thứ xảy ra trong thanh ghi hoặc bộ nhớ, trong cả hai trường hợp, CPU sẽ ghi đè lên bộ nhớ khi tràn.


Là ba chế độ cuối cùng được ký kết? (Không quan trọng đối với cái đầu tiên, vì nó là 2 bổ sung.)
Ded repeatator

1
@Ded repeatator: Theo Giới thiệu về Mô hình lập trình CPU Mill, có các opcodes khác nhau để bổ sung có chữ ký và bổ sung không dấu; Tôi hy vọng rằng cả hai opcodes sẽ hỗ trợ 4 chế độ (và có thể hoạt động trên các chiều rộng bit và vô hướng / vectơ khác nhau). Sau đó, một lần nữa, đó là phần cứng hơi ngay bây giờ;)
Matthieu M.

4

Để biết thêm câu trả lời của @ StevenBurnap, lý do điều này xảy ra là do cách máy tính hoạt động ở cấp độ máy.

Mảng của bạn được lưu trữ trong bộ nhớ (ví dụ: trong RAM). Khi một phép toán số học được thực hiện, giá trị trong bộ nhớ được sao chép vào các thanh ghi đầu vào của mạch thực hiện phép tính số học ( Đơn vị logic số học ALU ), sau đó thao tác được thực hiện trên dữ liệu trong các thanh ghi đầu vào, tạo ra kết quả trong thanh ghi đầu ra. Kết quả này sau đó được sao chép trở lại vào bộ nhớ tại địa chỉ chính xác trong bộ nhớ, khiến các vùng nhớ khác không bị ảnh hưởng.


4

Đầu tiên (giả sử tiêu chuẩn C99), bạn có thể muốn bao gồm <stdint.h>tiêu đề tiêu chuẩn và sử dụng một số loại được xác định ở đó, đáng chú ý int32_tlà chính xác là một số nguyên có 32 bit, hoặc uint64_tchính xác là một số nguyên không dấu 64 bit, v.v. Bạn có thể muốn sử dụng các loại như int_fast16_tvì lý do hiệu suất.

Đọc các câu trả lời khác giải thích rằng số học không dấu không bao giờ tràn (hoặc tràn) đến các vị trí bộ nhớ liền kề. Cảnh giác với hành vi không xác định trên tràn đã ký .

Sau đó, nếu bạn cần tính toán chính xác các số nguyên khổng lồ (ví dụ: bạn muốn tính giai thừa 1000 với tất cả 2568 chữ số của nó theo số thập phân), bạn muốn các số lớn hay còn gọi là số chính xác tùy ý (hoặc bignums). Các thuật toán cho số học bigint hiệu quả rất thông minh và thường yêu cầu sử dụng các hướng dẫn máy chuyên dụng (ví dụ: một số thêm từ với carry, nếu bộ xử lý của bạn có điều đó). Do đó, tôi thực sự khuyên bạn nên sử dụng một số thư viện bigint hiện có như GMPlib

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.