क्या कोई सी स्निपेट है जो कंपाइलर बिल्डिंस का उपयोग किए बिना कुशलता से अतिप्रवाह-सुरक्षित जोड़ की गणना करता है?


11

यहाँ एक C फ़ंक्शन है जो एक intऔर को जोड़ता है , यदि अतिप्रवाह होगा तो विफल होगा:

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;
}

दुर्भाग्य से यह जीसीसी या क्लैंग द्वारा अच्छी तरह से अनुकूलित नहीं है :

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

इस संस्करण के साथ __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;
        }
}

बेहतर अनुकूलित किया गया है :

safe_add(int*, int):
        xorl    %eax, %eax
        addl    (%rdi), %esi
        seto    %al
        jo      .L5
        movl    %esi, (%rdi)
        ret
.L5:
        movl    $-1, %eax
        ret

लेकिन मैं उत्सुक हूँ अगर वहाँ एक तरीका है कि जीसीसी या क्लैंग द्वारा प्रतिमान मिल जाएगा कि निर्माण का उपयोग किए बिना है।


1
मैं देख रहा हूं कि गुणन के संदर्भ में gcc.gnu.org/bugzilla/show_bug.cgi?id=48580 है। लेकिन इसके अलावा पैटर्न-मैच के लिए बहुत आसान होना चाहिए। मैं इसकी रिपोर्ट करूंगा।
तवीयन बार्न्स

रिपोर्ट की गई: gcc.gnu.org/bugzilla/show_bug.cgi?id=93742
Tavian Barnes

जवाबों:


6

Tthe सबसे अच्छा मैं एक साथ आया था, अगर आपके पास वास्तुकला के अतिप्रवाह झंडे तक पहुंच नहीं है, तो चीजों को करना है unsigned। यहाँ सभी बिट अंकगणित के बारे में सोचें कि हम केवल उच्चतम बिट में रुचि रखते हैं, जो हस्ताक्षरित मूल्यों के रूप में व्याख्या किए जाने पर साइन बिट है।

(सभी उस मोडुलो साइन त्रुटियों, मैंने इस पूरी जांच नहीं की थी, लेकिन मुझे उम्मीद है कि विचार स्पष्ट है)

#include <stdbool.h>

bool overadd(int a[static 1], int b) {
  unsigned A = a[0];
  unsigned B = b;
  // This computation will be done anyhow
  unsigned AB = A + B;
  // See if the sign bits are equal
  unsigned AeB = ~(A^B);
  unsigned AuAB = (A^AB);
  // The function result according to these should be:
  //
  // AeB \ AuAB | false | true
  //------------+-------+------
  // false      | false | false
  // true       | false | true
  //
  // So the expression to compute from the sign bits is (AeB & AuAB)

  // This is INT_MAX
  unsigned M = -1U/2;
  bool ret = (AeB & AuAB) > M;

  if (!ret) a[0] += b;
  return ret;
}

यदि आपको यूबी से मुक्त होने के अतिरिक्त एक संस्करण मिलता है, जैसे कि परमाणु एक, तो कोडांतरक बिना शाखा के भी होता है (लेकिन लॉक उपसर्ग के साथ)

#include <stdbool.h>
#include <stdatomic.h>
bool overadd(_Atomic(int) a[static 1], int b) {
  unsigned A = a[0];
  atomic_fetch_add_explicit(a, b, memory_order_relaxed);
  unsigned B = b;
  // This computation will be done anyhow
  unsigned AB = A + B;
  // See if the sign bits are equal
  unsigned AeB = ~(A^B);
  unsigned AuAB = (A^AB);
  // The function result according to these should be:
  //
  // AeB \ AuAB | false | true
  //------------+-------+------
  // false      | false | false
  // true       | false | true
  //
  // So the expression to compute from the sign bits is (AeB & AuAB)

  // This is INT_MAX
  unsigned M = -1U/2;
  bool ret = (AeB & AuAB) > M;
  return ret;
}

इसलिए अगर हमारे पास ऐसा कोई ऑपरेशन था, लेकिन इससे भी अधिक "आराम" से यह स्थिति और भी बेहतर हो सकती है।

टेक 3: यदि हम हस्ताक्षर किए गए परिणाम के लिए एक विशेष "कास्ट" का उपयोग करते हैं, तो यह अब शाखा मुक्त है:

#include <stdbool.h>
#include <stdatomic.h>

bool overadd(int a[static 1], int b) {
  unsigned A = a[0];
  //atomic_fetch_add_explicit(a, b, memory_order_relaxed);
  unsigned B = b;
  // This computation will be done anyhow
  unsigned AB = A + B;
  // See if the sign bits are equal
  unsigned AeB = ~(A^B);
  unsigned AuAB = (A^AB);
  // The function result according to these should be:
  //
  // AeB \ AuAB | false | true
  //------------+-------+------
  // false      | false | false
  // true       | false | true
  //
  // So the expression to compute from the sign bits is (AeB & AuAB)

  // This is INT_MAX
  unsigned M = -1U/2;
  unsigned res = (AeB & AuAB);
  signed N = M-1;
  N = -N - 1;
  a[0] =  ((AB > M) ? -(int)(-AB) : ((AB != M) ? (int)AB : N));
  return res > M;
}

2
DV नहीं, लेकिन मेरा मानना ​​है कि दूसरे XOR को नकारा नहीं जाना चाहिए। सभी प्रस्तावों का परीक्षण करने के लिए इस प्रयास को देखें ।
Bob__

मैंने ऐसा कुछ करने की कोशिश की, लेकिन यह काम नहीं कर सका। होनहार लगता है लेकिन काश जीसीसी मुहावरेदार कोड को अनुकूलित करता।
आर .. गिटहब स्टॉप हेल्पिंग आईसीई

1
@PSkocik, नहीं, यह साइन प्रतिनिधित्व पर निर्भर नहीं करता है, गणना पूरी तरह से की जाती है unsigned। लेकिन यह इस तथ्य पर निर्भर करता है कि अहस्ताक्षरित प्रकार ने केवल साइन बिट को मुखौटा नहीं बनाया है। (दोनों को अब C2x में गारंटी दी गई है, अर्थात, उन सभी आर्क के लिए पकड़ें जो हमें मिल सकते हैं)। फिर, यदि आप unsignedपरिणाम से अधिक है INT_MAX, तो आप परिणाम वापस नहीं कर सकते हैं , कि कार्यान्वयन को परिभाषित किया जाएगा और एक संकेत बढ़ा सकता है।
जेन्स गुस्ताद

1
@PSkocik, दुर्भाग्य से नहीं, कि समिति को उलटा लग रहा था। लेकिन यहां एक "टेक 3" है जो वास्तविक मेरी मशीन पर शाखाओं के बिना निकलता है।
जेन्स गस्टेड

1
आपको फिर से परेशान करने के लिए क्षमा करें, लेकिन मुझे लगता है कि आपको सही परिणाम प्राप्त करने के लिए टेक 3 को कुछ इस तरह बदलना चाहिए । यह होनहार लगता है , हालांकि।
Bob__

2

हस्ताक्षरित संचालन के साथ स्थिति अहस्ताक्षरित लोगों की तुलना में बहुत खराब है, और मैं हस्ताक्षरित जोड़ के लिए केवल एक पैटर्न देखता हूं, केवल क्लैंग के लिए और केवल जब एक व्यापक प्रकार उपलब्ध है:

int safe_add(int *value, int delta)
{
    long long result = (long long)*value + delta;

    if (result > INT_MAX || result < INT_MIN) {
        return -1;
    } else {
        *value = result;
        return 0;
    }
}

बजना बिल्कुल वैसा ही देता है एएसएम __builtin_add_overflow साथ के रूप में:

safe_add:                               # @safe_add
        addl    (%rdi), %esi
        movl    $-1, %eax
        jo      .LBB1_2
        movl    %esi, (%rdi)
        xorl    %eax, %eax
.LBB1_2:
        retq

अन्यथा, सबसे सरल समाधान जो मैं सोच सकता हूं वह यह है (इंटरफ़ेस के साथ जेन्स का उपयोग किया गया है):

_Bool overadd(int a[static 1], int b)
{
    // compute the unsigned sum
    unsigned u = (unsigned)a[0] + b;

    // convert it to signed
    int sum = u <= -1u / 2 ? (int)u : -1 - (int)(-1 - u);

    // see if it overflowed or not
    _Bool overflowed = (b > 0) != (sum > a[0]);

    // return the results
    a[0] = sum;
    return overflowed;
}

gcc और clang बहुत समान asm उत्पन्न करते हैं । gcc यह देता है:

overadd:
        movl    (%rdi), %ecx
        testl   %esi, %esi
        setg    %al
        leal    (%rcx,%rsi), %edx
        cmpl    %edx, %ecx
        movl    %edx, (%rdi)
        setl    %dl
        xorl    %edx, %eax
        ret

हम योग की गणना करना चाहते हैं unsigned , इसलिए unsignedउनमें से सभी को intएक साथ चिपकाए बिना सभी मूल्यों का प्रतिनिधित्व करने में सक्षम होना चाहिए। परिणाम को आसानी से परिवर्तित unsignedकरने के लिए int, इसके विपरीत भी उपयोगी है। कुल मिलाकर, दो का पूरक माना जाता है।

सभी लोकप्रिय प्लेटफार्मों पर मुझे लगता है कि हम इससे परिवर्तित कर सकते हैं unsigned करने के लिए intकी तरह एक साधारण असाइनमेंट द्वारा int sum = u;लेकिन, के रूप में जेन्स उल्लेख किया है, यहां तक कि C2x मानक के नवीनतम संस्करण यह एक संकेत को बढ़ाने के लिए अनुमति देता है। अगला सबसे स्वाभाविक तरीका कुछ ऐसा करना है: *(unsigned *)&sum = u;लेकिन बिना गद्दी के गैर-जाल संस्करण स्पष्ट रूप से हस्ताक्षरित और अहस्ताक्षरित प्रकारों के लिए भिन्न हो सकते हैं। तो ऊपर दिया गया उदाहरण कठिन रास्ता है। सौभाग्य से, दोनों gcc और clang इस मुश्किल रूपांतरण को दूर करते हैं।

पुनश्च दो भिन्नताओं की तुलना सीधे नहीं की जा सकती है क्योंकि उनका व्यवहार अलग है। पहला वाला मूल प्रश्न का अनुसरण करता है और *valueअतिप्रवाह के मामले में स्पष्ट नहीं होता है । दूसरा जेन्स के उत्तर का अनुसरण करता है और हमेशा पहले पैरामीटर द्वारा इंगित चर को क्लोब करता है लेकिन यह शाखाहीन है।


आप asm उत्पन्न दिखा सकते हैं?
R .. गिटहब स्टॉप हेल्पिंग IC

ओवर फ्लो चेक में xor द्वारा बदले गए समानता को gcc के साथ बेहतर आसम प्राप्त करने के लिए। जोड़ा गया।
अलेक्जेंडर चेरेपोनोव

1

सबसे अच्छा संस्करण मैं आ सकता है:

int safe_add(int *value, int delta) {
    long long t = *value + (long long)delta;
    if (t != ((int)t))
        return -1;
    *value = (int) t;
    return 0;
}

जो पैदा करता है:

safe_add(int*, int):
    movslq  %esi, %rax
    movslq  (%rdi), %rsi
    addq    %rax, %rsi
    movslq  %esi, %rax
    cmpq    %rsi, %rax
    jne     .L3
    movl    %eax, (%rdi)
    xorl    %eax, %eax
    ret
.L3:
    movl    $-1, %eax
    ret

मुझे आश्चर्य है कि अतिप्रवाह झंडे का उपयोग नहीं करता है। अभी भी स्पष्ट सीमा की जांच से बेहतर है, लेकिन यह लंबे समय तक जोड़ने के लिए सामान्य नहीं करता है।
तवीयन बार्न्स

@TavianBarnes आप सही कह रहे हैं, दुर्भाग्य से c में ओवरफ़्लो झंडे का उपयोग करने का कोई अच्छा तरीका नहीं है (संकलक विशिष्ट
बिल्डिंस

1
यह कोड हस्ताक्षरित अतिप्रवाह से ग्रस्त है, जो कि अपरिभाषित व्यवहार है।
Emacs ने मुझे नट

@emacsdrivesmenuts, आप सही कह रहे हैं, तुलना में कलाकार ओवरफ्लो कर सकते हैं।
जेन गुस्टेड

@emacsdrivesmenuts कलाकार अपरिभाषित नहीं हैं। जब की सीमा से बाहर int, एक व्यापक प्रकार से एक कलाकार या तो एक कार्यान्वयन-परिभाषित मूल्य का उत्पादन करेगा या एक संकेत बढ़ाएगा। सभी क्रियान्वयन मुझे इसकी परवाह करते हैं कि यह बिट पैटर्न को संरक्षित करने के लिए परिभाषित करता है जो सही काम करता है।
तवीयन बार्न्स

0

मैं संकलक (और दावा करते हुए) के दो पूरक पूरक बाइट्स के बिना संकेत ध्वज का उपयोग करने के लिए संकलक प्राप्त कर सकता था। इस तरह के कार्यान्वयन को एक टिप्पणी द्वारा एनोटेट की गई पंक्ति में आवश्यक व्यवहार प्राप्त करना चाहिए , हालांकि मुझे मानक में इस आवश्यकता की सकारात्मक औपचारिक पुष्टि नहीं मिल सकती है (और शायद कोई भी नहीं है)।

ध्यान दें कि निम्न कोड केवल सकारात्मक पूर्णांक जोड़ को संभालता है, लेकिन बढ़ाया जा सकता है।

int safe_add(int* lhs, int rhs) {
    _Static_assert(-1 == ~0, "integers are not two's complement");
    _Static_assert(
        1u << (sizeof(int) * CHAR_BIT - 1) == (unsigned) INT_MIN,
        "integers have padding bytes"
    );
    unsigned value = *lhs;
    value += rhs;
    if ((int) value < 0) return -1; // impl. def., 6.3.1.3/3
    *lhs = value;
    return 0;
}

यह क्लैंग और जीसीसी दोनों पर उपज देता है :

safe_add:
        add     esi, DWORD PTR [rdi]
        js      .L3
        mov     DWORD PTR [rdi], esi
        xor     eax, eax
        ret
.L3:
        mov     eax, -1
        ret

मुझे लगता है कि तुलना में कास्ट अपरिभाषित है। लेकिन आप इससे दूर हो सकते हैं जैसा कि मैं अपने जवाब में करता हूं। लेकिन फिर भी, सारा मज़ा सभी मामलों को कवर करने में सक्षम होने में है। आपका _Static_assertउद्देश्य एक उद्देश्य के रूप में नहीं है, क्योंकि यह किसी भी मौजूदा वास्तुकला पर तुच्छ रूप से सच है, और यहां तक ​​कि C2x के लिए भी लगाया जाएगा।
जेन्स गुस्ताद

2
@ जेन वास्तव में ऐसा लगता है कि कलाकारों का कार्यान्वयन-परिभाषित है, अपरिभाषित नहीं है, अगर मैं पढ़ रहा हूं (आईएसओ / आईईसी 9899: 2011) 6.3.1.3/3 सही ढंग से। क्या आप दोबारा जांच कर सकते हैं? (हालांकि, इसे नकारात्मक तर्कों तक पहुंचाना पूरी बात को जटिल बना देता है और अंततः आपके समाधान के समान है।)
कोनराड रूडोल्फ

आप सही कह रहे हैं, यह ipleitation परिभाषित है, लेकिन एक संकेत भी बढ़ा सकता है :(
जेन्स गुस्तेद

@ हाँ, तकनीकी रूप से मुझे लगता है कि एक दो के पूरक कार्यान्वयन में अभी भी पैडिंग बाइट्स हो सकते हैं। हो सकता है कि सैद्धांतिक सीमा की तुलना करके कोड को इसके लिए परीक्षण करना चाहिए INT_MAX। मैं पोस्ट को संपादित करूँगा। लेकिन फिर मुझे नहीं लगता कि इस कोड का प्रयोग वैसे भी किया जाना चाहिए।
कोनराड रुडोल्फ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.