Berikut adalah fungsi C yang menambahkan sebuah int
ke yang lain, gagal jika terjadi overflow:
int safe_add(int *value, int delta) {
if (*value >= 0) {
if (delta > INT_MAX - *value) {
return -1;
}
} else {
if (delta < INT_MIN - *value) {
return -1;
}
}
*value += delta;
return 0;
}
Sayangnya itu tidak dioptimalkan dengan baik oleh GCC atau Dentang:
safe_add(int*, int):
movl (%rdi), %eax
testl %eax, %eax
js .L2
movl $2147483647, %edx
subl %eax, %edx
cmpl %esi, %edx
jl .L6
.L4:
addl %esi, %eax
movl %eax, (%rdi)
xorl %eax, %eax
ret
.L2:
movl $-2147483648, %edx
subl %eax, %edx
cmpl %esi, %edx
jle .L4
.L6:
movl $-1, %eax
ret
Versi ini dengan __builtin_add_overflow()
int safe_add(int *value, int delta) {
int result;
if (__builtin_add_overflow(*value, delta, &result)) {
return -1;
} else {
*value = result;
return 0;
}
}
adalah dioptimalkan lebih baik :
safe_add(int*, int):
xorl %eax, %eax
addl (%rdi), %esi
seto %al
jo .L5
movl %esi, (%rdi)
ret
.L5:
movl $-1, %eax
ret
tapi saya ingin tahu apakah ada cara tanpa menggunakan builtin yang akan mendapatkan kecocokan pola oleh GCC atau Dentang.
c
gcc
optimization
clang
integer-overflow
Barnes Tavian
sumber
sumber
Jawaban:
Yang terbaik yang saya buat, jika Anda tidak memiliki akses ke bendera arsitektur yang meluap, adalah melakukan sesuatu
unsigned
. Pikirkan semua aritmatika bit di sini karena kami hanya tertarik pada bit tertinggi, yang merupakan bit tanda ketika ditafsirkan sebagai nilai yang ditandatangani.(Semua kesalahan tanda modulo, saya tidak memeriksa ini dengan baik, tapi saya harap idenya jelas)
Jika Anda menemukan versi tambahan yang bebas dari UB, seperti versi atom, assembler bahkan tanpa cabang (tetapi dengan awalan kunci)
Jadi jika kita melakukan operasi seperti itu, tetapi bahkan lebih "santai" ini dapat memperbaiki situasi lebih jauh.
Take3: Jika kami menggunakan "pemeran" khusus dari hasil yang tidak ditandatangani ke yang ditandatangani, sekarang ini bebas cabang:
sumber
unsigned
. Tapi itu tergantung pada fakta bahwa tipe unsigned bukan hanya sedikit saja yang disembunyikan. (Keduanya sekarang dijamin dalam C2x, yaitu, tahan untuk semua lengkungan yang bisa kita temukan). Kemudian, Anda tidak dapat mengembalikanunsigned
hasilnya jika lebih besar dari ituINT_MAX
, yang akan menjadi implementasi yang ditentukan dan dapat meningkatkan sinyal.Situasi dengan operasi yang ditandatangani jauh lebih buruk daripada yang tidak ditandatangani, dan saya hanya melihat satu pola untuk penambahan yang ditandatangani, hanya untuk dentang dan hanya ketika jenis yang lebih luas tersedia:
dentang memberikan asm persis sama dengan __builtin_add_overflow:
Kalau tidak, solusi paling sederhana yang dapat saya pikirkan adalah ini (dengan antarmuka seperti yang digunakan Jens):
gcc dan dentang menghasilkan asm sangat mirip . gcc memberikan ini:
Kami ingin menghitung jumlahnya
unsigned
, jadiunsigned
harus dapat mewakili semua nilaiint
tanpa ada yang menyatu. Untuk dengan mudah mengkonversi hasil dariunsigned
keint
, yang sebaliknya juga berguna. Secara keseluruhan, komplemen dua diasumsikan.Pada semua platform populer, saya pikir kita dapat mengkonversi dari
unsigned
keint
dengan penugasan sederhana sepertiint sum = u;
tetapi, seperti yang disebutkan Jens, bahkan varian terbaru dari standar C2x memungkinkannya untuk menaikkan sinyal. Cara paling alami berikutnya adalah melakukan sesuatu seperti itu:*(unsigned *)&sum = u;
tetapi varian padding non-trap tampaknya dapat berbeda untuk tipe yang ditandatangani dan tidak ditandatangani. Jadi contoh di atas berjalan dengan susah payah. Untungnya, baik gcc dan dentang mengoptimalkan konversi rumit ini.PS Dua varian di atas tidak dapat dibandingkan secara langsung karena mereka memiliki perilaku yang berbeda. Yang pertama mengikuti pertanyaan awal dan tidak mengalahkan
*value
jika terjadi overflow. Yang kedua mengikuti jawaban dari Jens dan selalu clobbers variabel yang ditunjukkan oleh parameter pertama tetapi itu tidak memiliki cabang.sumber
versi terbaik yang bisa saya dapatkan adalah:
yang menghasilkan:
sumber
int
, gips dari tipe yang lebih luas akan menghasilkan nilai yang ditentukan implementasi atau menaikkan sinyal. Semua implementasi yang saya pedulikan mendefinisikannya untuk mempertahankan pola bit yang melakukan hal yang benar.Saya bisa mendapatkan kompiler untuk menggunakan tanda bendera dengan mengasumsikan (dan menyatakan) representasi komplemen dua tanpa padding byte. Implementasi seperti itu harus menghasilkan perilaku yang diperlukan dalam baris yang dijelaskan oleh komentar, walaupun saya tidak dapat menemukan konfirmasi formal positif dari persyaratan ini dalam standar (dan mungkin tidak ada).
Perhatikan bahwa kode berikut hanya menangani penambahan bilangan bulat positif, tetapi dapat diperpanjang.
Ini menghasilkan clang dan GCC:
sumber
_Static_assert
Tujuan Anda tidak banyak, karena ini sepele pada arsitektur saat ini, dan bahkan akan dikenakan untuk C2x.INT_MAX
. Saya akan mengedit posting. Tapi sekali lagi saya tidak berpikir kode ini harus digunakan dalam praktiknya.