কোনও সি স্নিপেট রয়েছে যা সংকলক বিল্টিনগুলি ব্যবহার না করে ওভারফ্লো-নিরাপদ সংযোজনকে দক্ষতার সাথে গণনা করে?


11

এখানে একটি সি ফাংশন যা অপরটিতে যুক্ত 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
বার্নস

উত্তর:


6

সেরাটি আমি নিয়ে এসেছি, যদি আপনার কাছে স্থাপত্যের ওভারফ্লো পতাকাটিতে অ্যাক্সেস না থাকে তবে কিছু করতে হবে 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
ডিভি নয়, তবে আমি বিশ্বাস করি যে দ্বিতীয় এক্সওআরটিকে অবহেলা করা উচিত নয়। উদাহরণস্বরূপ দেখুন সমস্ত প্রস্তাব পরীক্ষা করার এই প্রচেষ্টা
বব__

আমি এই জাতীয় কিছু চেষ্টা করেছি কিন্তু এটি কাজ করতে পারেনি। প্রতিশ্রুতিবদ্ধ মনে হচ্ছে তবে আমি আশা করি জিসিসি আইডিয়োমেটিক কোডটি অনুকূলিত করে।
আর .. গীটহাব স্টপ হেল্পিং আইসিসি

1
@ স্পোকিক, এটি সাইন প্রতিনিধির উপর নির্ভর করে না, গণনা সম্পূর্ণরূপে সম্পন্ন হয়েছে unsigned। তবে এটি নির্ভর করে যে স্বাক্ষরবিহীন প্রকারটি সাইন বিটটি কেবল মুখোমুখি হয়নি। (উভয়ই এখন সি 2 এক্স-এ গ্যারান্টিযুক্ত, অর্থাৎ, আমরা যে সন্ধান পেতে পারি তার জন্য সংরক্ষণ করুন)) তারপরে, আপনি unsignedফলাফলটি আর কাস্ট করতে পারবেন না যদি এটি এর চেয়ে বেশি হয় তবে এটি INT_MAXবাস্তবায়ন সংজ্ঞায়িত হয়ে সংকেত বাড়াতে পারে।
জেনস গুস্ট্ট

1
@ স্পোকিক, দুর্ভাগ্যক্রমে নয়, কমিটির কাছে এটি বিদ্রোহী বলে মনে হয়েছিল। তবে এখানে একটি "টেক 3" রয়েছে যা আমার মেশিনে শাখা ছাড়াই আসল।
জেনস গুস্ট্ট

1
আপনাকে আবার বিরক্ত করার জন্য দুঃখিত, তবে আমি মনে করি সঠিক ফলাফল পাওয়ার জন্য আপনার নেওয়া 3 কে এই জাতীয় কিছুতে পরিবর্তন করা উচিত । যদিও এটি আশ্বাসজনক বলে মনে হচ্ছে ।
বব__

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

ঝাঁকুনি __ বিল্টিন_এডডি_ওভারফ্লো হিসাবে ঠিক একই asm দেয় :

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

জিসিসি এবং ঝনঝনাই খুব একই রকম এসএমএস উত্পন্ন করে । জিসিসি এটি দেয়:

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;তবে প্যাডিংয়ের নন-ট্র্যাপ বৈকল্পিকগুলি স্বাক্ষরযুক্ত এবং স্বাক্ষরবিহীন প্রকারের জন্য পৃথক হতে পারে। সুতরাং উপরের উদাহরণটি হার্ড পথ যায়। ভাগ্যক্রমে, জিসিসি এবং ক্লাং উভয়ই এই কৌশলটি রূপান্তরটিকে দূরে সরিয়ে নিয়ে যায়।

পিএস উপরের দুটি ভেরিয়েন্টগুলির সাথে আলাদা আচরণ করার কারণে সরাসরি তুলনা করা যায়নি। প্রথমটি মূল প্রশ্নটি অনুসরণ করে এবং অতিরিক্ত *valueপ্রবাহের ক্ষেত্রে আঁকড়ে ধরে না। দ্বিতীয়টি জেনসের উত্তর অনুসরণ করে এবং সর্বদা প্রথম প্যারামিটারের দ্বারা চিহ্নিত ভেরিয়েবলটি ক্লাববার্স কিন্তু এটি শাখাবিহীন।


আপনি কি তৈরি asm দেখাতে পারেন?
আর .. গিথহাব বন্ধ হেল্পিং আইসিসি

জিসিসির সাথে আরও ভাল asm পেতে ওভারফ্লো চেকটিতে xor দ্বারা সমতা প্রতিস্থাপন করা হয়েছে। সংযুক্ত asm।
আলেকজান্ডার চেরেপনভ

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

আমি অবাক হয়েছি এমনকি এটি ওভারফ্লো পতাকা ব্যবহার করে না। সুস্পষ্ট পরিসীমা চেকগুলির তুলনায় এখনও অনেক ভাল, তবে এটি দীর্ঘতর দীর্ঘস্থায়ী যোগ করার ক্ষেত্রে সাধারণীকরণ করে না।
তাভিয়ান বার্নস

@ টাভিয়ান বার্নস আপনি ঠিক বলেছেন, দুর্ভাগ্যক্রমে সিতে ওভারফ্লো পতাকা ব্যবহার করার ভাল কোনও উপায় নেই (সংকলক নির্দিষ্ট বিল্টিনগুলি বাদে)
আইয়া বার্সভ

1
এই কোডটি স্বাক্ষরিত ওভারফ্লোতে ভুগছে যা অনির্ধারিত আচরণ।
ইমাস আমাকে

@ ইমাস্যাকড্রিভেনসম্যান্টস, আপনি ঠিক বলেছেন, তুলনা কাস্টটি উপচে পড়তে পারে।
জেনস গুস্ট্ট

@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কোনও উদ্দেশ্য খুব বেশি কাজ করে না, কারণ এটি বর্তমানের কোনও স্থাপত্যে তুচ্ছভাবে সত্য, এবং এমনকি সি 2 এক্সের জন্য আরোপিত হবে।
জেনস গুস্ট্ট

2
@ জেনস আসলে মনে হচ্ছে কাস্টটি বাস্তবায়ন-সংজ্ঞায়িত, অপরিজ্ঞাত নয়, যদি আমি পড়ছি (আইএসও / আইসিসি 9899: 2011) 6.3.1.3/3 সঠিকভাবে। আপনি কি ডাবল-চেক করতে পারেন? (তবে, নেতিবাচক যুক্তিগুলিতে এটি প্রসারিত করা পুরো জিনিসটিকে বরং বিশৃঙ্খলাবদ্ধ করে এবং শেষ পর্যন্ত আপনার সমাধানের সাথে সমান করে তোলে))
কনরাড রুডল্ফ

আপনি ঠিক বলেছেন, এটি সংশ্লেষকে সংজ্ঞায়িত করা হয়েছে তবে এটি একটি সংকেতও বাড়িয়ে তুলতে পারে :(
জেনস গুস্টেট

@ জেনস হ্যাঁ, প্রযুক্তিগতভাবে আমি অনুমান করি যে দু'জনের পরিপূরক বাস্তবায়নে এখনও প্যাডিং বাইট থাকতে পারে। তাত্ত্বিক পরিসীমা তুলনা করে কোডের জন্য এটি পরীক্ষা করা উচিত INT_MAX। আমি পোস্টটি সম্পাদনা করব। তবে তারপরেও আমি মনে করি না যে এই কোডটি যাইহোক বাস্তবে ব্যবহার করা উচিত।
কনরাড রুডলফ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.