Thoạt nhìn, câu hỏi này có vẻ giống như một bản sao của Làm thế nào để phát hiện tràn số nguyên? , tuy nhiên nó thực sự khác biệt đáng kể.
Tôi đã tìm thấy rằng trong khi phát hiện một lỗi tràn số nguyên unsigned là khá tầm thường, phát hiện một ký tràn trong C / C ++ thực sự là khó khăn hơn so với hầu hết mọi người nghĩ.
Cách rõ ràng nhất, nhưng ngây thơ, để làm điều đó sẽ là một cái gì đó như:
int add(int lhs, int rhs)
{
int sum = lhs + rhs;
if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
/* an overflow has occurred */
abort();
}
return sum;
}
Vấn đề với điều này là theo tiêu chuẩn C, tràn số nguyên có dấu là hành vi không xác định. Nói cách khác, theo tiêu chuẩn, ngay khi bạn gây ra tràn đã ký, chương trình của bạn cũng không hợp lệ như thể bạn đã tham chiếu đến một con trỏ null. Vì vậy, bạn không thể gây ra hành vi không xác định và sau đó cố gắng phát hiện tràn sau thực tế, như trong ví dụ kiểm tra sau điều kiện ở trên.
Mặc dù kiểm tra ở trên có thể hoạt động trên nhiều trình biên dịch, nhưng bạn không thể tin tưởng vào nó. Trên thực tế, vì tiêu chuẩn C cho biết tràn số nguyên có dấu là không xác định, một số trình biên dịch (như GCC) sẽ tối ưu hóa việc kiểm tra ở trên khi các cờ tối ưu hóa được thiết lập, vì trình biên dịch giả định việc tràn số có ký là không thể. Điều này hoàn toàn phá vỡ nỗ lực kiểm tra tràn.
Vì vậy, một cách khả thi khác để kiểm tra tràn sẽ là:
int add(int lhs, int rhs)
{
if (lhs >= 0 && rhs >= 0) {
if (INT_MAX - lhs <= rhs) {
/* overflow has occurred */
abort();
}
}
else if (lhs < 0 && rhs < 0) {
if (lhs <= INT_MIN - rhs) {
/* overflow has occurred */
abort();
}
}
return lhs + rhs;
}
Điều này có vẻ hứa hẹn hơn, vì chúng tôi không thực sự cộng hai số nguyên với nhau cho đến khi chúng tôi đảm bảo trước rằng thực hiện một phép cộng như vậy sẽ không dẫn đến tràn. Do đó, chúng tôi không gây ra bất kỳ hành vi không xác định nào.
Tuy nhiên, giải pháp này không may là kém hiệu quả hơn nhiều so với giải pháp ban đầu, vì bạn phải thực hiện một phép tính trừ chỉ để kiểm tra xem phép toán cộng của bạn có hoạt động hay không. Và ngay cả khi bạn không quan tâm đến cú đánh hiệu suất (nhỏ) này, tôi vẫn không hoàn toàn tin rằng giải pháp này là phù hợp. Biểu thức lhs <= INT_MIN - rhs
có vẻ giống hệt như loại biểu thức mà trình biên dịch có thể tối ưu hóa loại bỏ, nghĩ rằng việc tràn có dấu là không thể.
Vì vậy, có một giải pháp tốt hơn ở đây? Một cái gì đó được đảm bảo 1) không gây ra hành vi không xác định và 2) không cung cấp cho trình biên dịch cơ hội để tối ưu hóa việc kiểm tra tràn? Tôi đã nghĩ rằng có thể có một số cách để làm điều đó bằng cách chuyển cả hai toán hạng thành không dấu và thực hiện kiểm tra bằng cách lăn số học phần bù của riêng bạn, nhưng tôi không thực sự chắc chắn về cách làm điều đó.