यह लूप "चेतावनी: पुनरावृत्ति 3u अपरिभाषित व्यवहार" और 4 से अधिक लाइनों का उत्पादन क्यों करता है?


162

इसे संकलित करना:

#include <iostream>

int main()
{
    for (int i = 0; i < 4; ++i)
        std::cout << i*1000000000 << std::endl;
}

और gccनिम्नलिखित चेतावनी उत्पन्न करता है:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

मैं समझता हूं कि एक हस्ताक्षरित पूर्णांक अतिप्रवाह है।

जो मुझे नहीं मिल रहा है वह iमूल्य उस अतिप्रवाह ऑपरेशन से क्यों टूटा है?

मैंने उत्तर पढ़ा है कि G86 के साथ x86 पर पूर्णांक ओवरफ़्लो क्यों होता है? , लेकिन मैं अभी भी स्पष्ट नहीं हूं कि ऐसा क्यों होता है - मुझे लगता है कि "अपरिभाषित" का अर्थ है "कुछ भी हो सकता है", लेकिन इस विशिष्ट व्यवहार का अंतर्निहित कारण क्या है ?

ऑनलाइन: http://ideone.com/dMrRKR

संकलक: gcc (4.8)


49
हस्ताक्षरित पूर्णांक अतिप्रवाह => अपरिभाषित व्यवहार => नाक दाएं। लेकिन मुझे स्वीकार करना होगा, यह उदाहरण काफी अच्छा है।
dyp

1
असेंबली आउटपुट: goo.gl/TtPmZn
ब्रायन चेन

1
साथ जीसीसी 4.8 पर होता है O2, और O3झंडा, लेकिन नहीं O0याO1
एलेक्स

3
@dyp जब मैंने Nasal Daemons को पढ़ा तो मैंने "imgur हंसी" की जिसमें आपकी नाक से थोड़ी सांस लेने पर कुछ अजीब हो जाता है। और फिर मुझे एहसास हुआ ... मुझे एक नसल डेमन द्वारा शापित होना चाहिए!
corsiKa

4
यह बुकमार्क तो मैं यह अगली बार जब कोई जवाब देते हैं लिंक कर सकते हैं "यह तकनीकी रूप से यूबी है, लेकिन ऐसा करना चाहिए बात " :)
एम एम

जवाबों:


107

पूर्णांक ओवरफ़्लो पर हस्ताक्षर किए गए (कड़ाई से बोलते हुए, "अहस्ताक्षरित पूर्णांक ओवरफ़्लो" जैसी कोई चीज़ नहीं है) का अर्थ अपरिभाषित व्यवहार है । और इसका मतलब यह है कि कुछ भी हो सकता है, और सी ++ के नियमों के तहत ऐसा होने पर चर्चा करने का कोई मतलब नहीं है।

C ++ 11 ड्राफ्ट N3337: .45.4: 1

यदि किसी अभिव्यक्ति के मूल्यांकन के दौरान, परिणाम गणितीय रूप से not ned नहीं है या उसके प्रकार के लिए प्रतिनिधित्व योग्य मूल्यों की सीमा में नहीं है, तो व्यवहार unde ned है। [नोट: C ++ के अधिकांश मौजूदा कार्यान्वयन। Ows पर पूर्णांक को अनदेखा करते हैं। शून्य से विभाजन का उपचार, एक शून्य विभाजक का उपयोग करके शेष बना रहा है, और सभी point ओटिंग बिंदु अपवाद मशीनों के बीच भिन्न होते हैं, और आमतौर पर एक पुस्तकालय फ़ंक्शन द्वारा समायोज्य होता है। ध्यान दें]

आपका कोड g++ -O3चेतावनी के साथ संकलित है (बिना भी -Wall)

a.cpp: In function 'int main()':
a.cpp:11:18: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^
a.cpp:9:2: note: containing loop
  for (int i = 0; i < 4; ++i)
  ^

एकमात्र तरीका हम यह विश्लेषण कर सकते हैं कि कार्यक्रम क्या कर रहा है, उत्पन्न विधानसभा कोड को पढ़कर।

यहाँ पूर्ण विधानसभा सूची है:

    .file   "a.cpp"
    .section    .text$_ZNKSt5ctypeIcE8do_widenEc,"x"
    .linkonce discard
    .align 2
LCOLDB0:
LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  __ZNKSt5ctypeIcE8do_widenEc
    .def    __ZNKSt5ctypeIcE8do_widenEc;    .scl    2;  .type   32; .endef
__ZNKSt5ctypeIcE8do_widenEc:
LFB860:
    .cfi_startproc
    movzbl  4(%esp), %eax
    ret $4
    .cfi_endproc
LFE860:
LCOLDE0:
LHOTE0:
    .section    .text.unlikely,"x"
LCOLDB1:
    .text
LHOTB1:
    .p2align 4,,15
    .def    ___tcf_0;   .scl    3;  .type   32; .endef
___tcf_0:
LFB1091:
    .cfi_startproc
    movl    $__ZStL8__ioinit, %ecx
    jmp __ZNSt8ios_base4InitD1Ev
    .cfi_endproc
LFE1091:
    .section    .text.unlikely,"x"
LCOLDE1:
    .text
LHOTE1:
    .def    ___main;    .scl    2;  .type   32; .endef
    .section    .text.unlikely,"x"
LCOLDB2:
    .section    .text.startup,"x"
LHOTB2:
    .p2align 4,,15
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB1084:
    .cfi_startproc
    leal    4(%esp), %ecx
    .cfi_def_cfa 1, 0
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    .cfi_escape 0x10,0x5,0x2,0x75,0
    movl    %esp, %ebp
    pushl   %edi
    pushl   %esi
    pushl   %ebx
    pushl   %ecx
    .cfi_escape 0xf,0x3,0x75,0x70,0x6
    .cfi_escape 0x10,0x7,0x2,0x75,0x7c
    .cfi_escape 0x10,0x6,0x2,0x75,0x78
    .cfi_escape 0x10,0x3,0x2,0x75,0x74
    xorl    %edi, %edi
    subl    $24, %esp
    call    ___main
L4:
    movl    %edi, (%esp)
    movl    $__ZSt4cout, %ecx
    call    __ZNSolsEi
    movl    %eax, %esi
    movl    (%eax), %eax
    subl    $4, %esp
    movl    -12(%eax), %eax
    movl    124(%esi,%eax), %ebx
    testl   %ebx, %ebx
    je  L15
    cmpb    $0, 28(%ebx)
    je  L5
    movsbl  39(%ebx), %eax
L6:
    movl    %esi, %ecx
    movl    %eax, (%esp)
    addl    $1000000000, %edi
    call    __ZNSo3putEc
    subl    $4, %esp
    movl    %eax, %ecx
    call    __ZNSo5flushEv
    jmp L4
    .p2align 4,,10
L5:
    movl    %ebx, %ecx
    call    __ZNKSt5ctypeIcE13_M_widen_initEv
    movl    (%ebx), %eax
    movl    24(%eax), %edx
    movl    $10, %eax
    cmpl    $__ZNKSt5ctypeIcE8do_widenEc, %edx
    je  L6
    movl    $10, (%esp)
    movl    %ebx, %ecx
    call    *%edx
    movsbl  %al, %eax
    pushl   %edx
    jmp L6
L15:
    call    __ZSt16__throw_bad_castv
    .cfi_endproc
LFE1084:
    .section    .text.unlikely,"x"
LCOLDE2:
    .section    .text.startup,"x"
LHOTE2:
    .section    .text.unlikely,"x"
LCOLDB3:
    .section    .text.startup,"x"
LHOTB3:
    .p2align 4,,15
    .def    __GLOBAL__sub_I_main;   .scl    3;  .type   32; .endef
__GLOBAL__sub_I_main:
LFB1092:
    .cfi_startproc
    subl    $28, %esp
    .cfi_def_cfa_offset 32
    movl    $__ZStL8__ioinit, %ecx
    call    __ZNSt8ios_base4InitC1Ev
    movl    $___tcf_0, (%esp)
    call    _atexit
    addl    $28, %esp
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc
LFE1092:
    .section    .text.unlikely,"x"
LCOLDE3:
    .section    .text.startup,"x"
LHOTE3:
    .section    .ctors,"w"
    .align 4
    .long   __GLOBAL__sub_I_main
.lcomm __ZStL8__ioinit,1,1
    .ident  "GCC: (i686-posix-dwarf-rev1, Built by MinGW-W64 project) 4.9.0"
    .def    __ZNSt8ios_base4InitD1Ev;   .scl    2;  .type   32; .endef
    .def    __ZNSolsEi; .scl    2;  .type   32; .endef
    .def    __ZNSo3putEc;   .scl    2;  .type   32; .endef
    .def    __ZNSo5flushEv; .scl    2;  .type   32; .endef
    .def    __ZNKSt5ctypeIcE13_M_widen_initEv;  .scl    2;  .type   32; .endef
    .def    __ZSt16__throw_bad_castv;   .scl    2;  .type   32; .endef
    .def    __ZNSt8ios_base4InitC1Ev;   .scl    2;  .type   32; .endef
    .def    _atexit;    .scl    2;  .type   32; .endef

मैं मुश्किल से विधानसभा भी पढ़ सकता हूं, लेकिन यहां तक ​​कि मैं addl $1000000000, %ediरेखा भी देख सकता हूं । परिणामस्वरूप कोड अधिक दिखता है

for(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000)
    std::cout << i << std::endl;

@TC की यह टिप्पणी:

मुझे संदेह है कि यह कुछ ऐसा है: (1) क्योंकि i2 से बड़े किसी भी मूल्य वाले प्रत्येक पुनरावृत्ति में अपरिभाषित व्यवहार होता है -> (2) हम यह मान सकते हैं कि i <= 2अनुकूलन उद्देश्यों के लिए -> (3) लूप की स्थिति हमेशा सही होती है -> (4) ) यह एक अनंत लूप में दूर अनुकूलित है।

मुझे बिना किसी अपरिभाषित व्यवहार के, ओपी कोड के विधानसभा कोड की तुलना निम्नलिखित कोड के विधानसभा कोड से करने का विचार दिया।

#include <iostream>

int main()
{
    // changed the termination condition
    for (int i = 0; i < 3; ++i)
        std::cout << i*1000000000 << std::endl;
}

और, वास्तव में, सही कोड में समाप्ति की स्थिति है।

    ; ...snip...
L6:
    mov ecx, edi
    mov DWORD PTR [esp], eax
    add esi, 1000000000
    call    __ZNSo3putEc
    sub esp, 4
    mov ecx, eax
    call    __ZNSo5flushEv
    cmp esi, -1294967296 // here it is
    jne L7
    lea esp, [ebp-16]
    xor eax, eax
    pop ecx
    ; ...snip...

OMG, यह पूरी तरह से स्पष्ट नहीं है! यह सही नहीं है! मैं आग से परीक्षण की मांग करता हूं!

इससे निपटने के लिए, आपने छोटी गाड़ी कोड लिखा है और आपको बुरा महसूस करना चाहिए। परिणाम भुगतना।

... या, वैकल्पिक रूप से, बेहतर डायग्नोस्टिक्स और बेहतर डिबगिंग टूल का उचित उपयोग करें - यही उनके लिए है:

  • सभी चेतावनियों को सक्षम करें

    • -Wallgcc विकल्प है जो बिना किसी गलत सकारात्मक के सभी उपयोगी चेतावनियों को सक्षम करता है। यह एक नंगे न्यूनतम है जिसे आपको हमेशा उपयोग करना चाहिए।
    • जीसीसी के पास कई अन्य चेतावनी विकल्प हैं , हालांकि, वे इसके साथ सक्षम नहीं हैं -Wallक्योंकि वे झूठी सकारात्मक चेतावनी दे सकते हैं
    • विजुअल C ++ दुर्भाग्य से उपयोगी चेतावनी देने की क्षमता से पीछे है। कम से कम आईडीई डिफ़ॉल्ट रूप से कुछ सक्षम करता है।
  • डीबगिंग के लिए डीबग ध्वज का उपयोग करें

    • पूर्णांक ओवरफ़्लो के -ftrapvलिए प्रोग्राम को ओवरफ़्लो पर ट्रैप करता है,
    • बजना संकलक इस के लिए उत्कृष्ट है: -fcatch-undefined-behaviorअपरिभाषित व्यवहार के उदाहरण के एक बहुत फैल जाती है (ध्यान दें: "a lot of" != "all of them")

मेरे पास मेरे द्वारा नहीं लिखे गए एक प्रोग्राम की स्पैगेटी मेस है जिसे कल भेजने की आवश्यकता है! मदद !!!!!! 111oneone

Gcc का प्रयोग करें -fwrapv

यह विकल्प कंपाइलर को यह मानने का निर्देश देता है कि जुड़वा-पूरक प्रतिनिधित्व का उपयोग करते हुए इसके अलावा, घटाव और गुणा गुणा पर हस्ताक्षर किए गए अंकगणित ओवरफ्लो करते हैं।

1 - यह नियम "अहस्ताक्षरित पूर्णांक ओवरफ़्लो" पर लागू नहीं होता है, जैसा कि .13.9.1.4 कहता है

अहस्ताक्षरित पूर्णांक, जिन्हें अहस्ताक्षरित घोषित किया गया है, अंकगणितीय मोडुलो 2 एन के नियमों का पालन करेंगे जहां एन पूर्णांक के उस विशेष आकार के मूल्य प्रतिनिधित्व में बिट्स की संख्या है।

और उदाहरण के लिए परिणाम UINT_MAX + 1गणितीय रूप से परिभाषित किया गया है - अंकगणितीय मोडुलो 2 एन के नियमों द्वारा


7
मैं अभी भी वास्तव में नहीं समझ पा रहा हूँ कि यहाँ क्या हो रहा है ... iखुद क्यों प्रभावित हो रहा है ? सामान्य अपरिभाषित व्यवहार में इस तरह के अजीब दुष्प्रभाव नहीं होते हैं, आखिरकार, i*100000000एक
प्रतिद्वंद्विता

26
मुझे संदेह है कि यह कुछ ऐसा है: (1) क्योंकि i2 से बड़े किसी भी मूल्य वाले प्रत्येक पुनरावृत्ति में अपरिभाषित व्यवहार होता है -> (2) हम यह मान सकते हैं कि i <= 2अनुकूलन उद्देश्यों के लिए -> (3) लूप की स्थिति हमेशा सही होती है -> (4) ) यह एक अनंत लूप में दूर अनुकूलित है।
टीसी

28
@vsoftco: क्या चल रहा है ताकत में कमी का मामला है , विशेष रूप से, प्रेरण चर उन्मूलन । संकलक कोड का उत्सर्जन करके गुणन को समाप्त करता है जो इसके बजाय i1e9 प्रत्येक वृद्धि (और तदनुसार लूप को बदलकर) बढ़ाता है। यह "के रूप में अगर" नियम के तहत एक पूरी तरह से वैध अनुकूलन है क्योंकि यह कार्यक्रम अंतर नहीं देख सकता है यह अच्छी तरह से व्यवहार कर रहे थे। काश, यह नहीं है, और अनुकूलन "लीक"।
जोहान्सड

8
@JohannesD ने इस ब्रेक के कारण का उल्लेख किया। हालाँकि, यह एक बुरा अनुकूलन है क्योंकि लूप समाप्ति की स्थिति में अतिप्रवाह शामिल नहीं है। शक्ति में कमी का उपयोग ठीक था - मुझे नहीं पता कि प्रोसेसर में गुणक (4 * 100000000) के साथ क्या करेगा (100000000 + 100000000 + 100000000 + 100000000) के साथ अलग होगा, और यह अपरिभाषित होने पर वापस गिर जाएगा - कौन जानता है "उचित है। लेकिन एक अच्छी तरह से व्यवहार किया जाने वाला लूप होना चाहिए, जो 4 बार निष्पादित होता है और अपरिभाषित परिणाम उत्पन्न करता है, कुछ के साथ जो 4 से अधिक बार निष्पादित होता है "क्योंकि यह अपरिभाषित है!" मूर्खतापूर्ण है।
ऑस्टिन

14
@ जूलीनएस्टिन हालांकि यह आपके लिए मूर्खतापूर्ण हो सकता है, यह पूरी तरह से कानूनी है। सकारात्मक पक्ष पर, संकलक आपको इसके बारे में चेतावनी देता है।
सहस्राब्दी

68

संक्षिप्त उत्तर, gccविशेष रूप से इस समस्या का दस्तावेजीकरण किया गया है, हम देख सकते हैं कि gcc 4.8 के जारी नोटों में कहा गया है ( जोर मेरा आगे बढ़ना ):

जीसीसी अब भाषा के मानकों पर लगाए गए अवरोधों का उपयोग करके छोरों की पुनरावृत्तियों की संख्या के लिए एक ऊपरी बाध्य करने के लिए एक अधिक आक्रामक विश्लेषण का उपयोग करता है । यह गैर-अनुरूपण कार्यक्रमों के कारण अपेक्षा के अनुरूप काम नहीं कर सकता है, जैसे कि स्पेश सीपीयू 2006 464.h264ref और 416। इस आक्रामक विश्लेषण को निष्क्रिय करने के लिए एक नया विकल्प, -ऑन-आक्रामक-लूप-ऑप्टिमाइज़ेशन जोड़ा गया। कुछ छोरों में जिन्हें पुनरावृत्तियों की निरंतर संख्या ज्ञात है, लेकिन अपरिभाषित व्यवहार को पहुंचने से पहले या अंतिम पुनरावृत्ति के दौरान लूप में होने के लिए जाना जाता है, GCC लूप में अपरिभाषित व्यवहार के बारे में चेतावनी देगा कि पुनरावृत्तियों की संख्या के निचले स्तर को प्राप्त करने के बजाय। पाश के लिए। चेतावनी को निष्क्रिय-आक्रामक-पाश-अनुकूलन के साथ अक्षम किया जा सकता है।

और वास्तव में अगर हम -fno-aggressive-loop-optimizationsअनंत लूप व्यवहार का उपयोग करते हैं , तो इसे बंद कर देना चाहिए और यह उन सभी मामलों में होता है जिन्हें मैंने परीक्षण किया है।

लंबे समय से उत्तर यह जानने के साथ शुरू होता है कि हस्ताक्षरित पूर्णांक ओवरफ्लो सी + + मानक अनुभाग 5 एक्सप्रेशंस पैराग्राफ 4 के मसौदे को देखकर अपरिभाषित व्यवहार है :

यदि किसी अभिव्यक्ति के मूल्यांकन के दौरान, परिणाम गणितीय रूप से परिभाषित नहीं है या अपने प्रकार के लिए प्रतिनिधित्व योग्य मूल्यों की सीमा में नहीं है, तो व्यवहार अपरिभाषित है । [नोट: C ++ के अधिकांश मौजूदा कार्यान्वयन पूर्णांक ओवरफ्लो को अनदेखा करते हैं। शून्य से विभाजन का उपचार, एक शून्य विभाजक का उपयोग करके शेष का गठन, और सभी फ़्लोटिंग पॉइंट अपवाद मशीनों के बीच भिन्न होते हैं, और आमतौर पर एक लाइब्रेरी फ़ंक्शन द्वारा समायोज्य होता है। ध्यान दें

हम जानते हैं कि मानक का कहना है कि अपरिभाषित व्यवहार उस नोट से अप्रत्याशित है जो परिभाषा के साथ आता है:

[नोट: यह अंतर्राष्ट्रीय मानक व्यवहार की किसी भी स्पष्ट परिभाषा को छोड़ देता है या जब कोई प्रोग्राम गलत निर्माण या गलत डेटा का उपयोग करता है, तो अपरिभाषित व्यवहार की उम्मीद की जा सकती है। अनुज्ञेय अपरिभाषित व्यवहार की स्थिति की अनदेखी पूरी तरह से अप्रत्याशित परिणामों के साथ होती है , अनुवाद या कार्यक्रम निष्पादन के दौरान व्यवहार करने के लिए पर्यावरण की एक प्रलेखित तरीके से विशेषता (एक नैदानिक ​​संदेश जारी किए बिना या बिना), अनुवाद या निष्पादन को समाप्त करने (जारी करने के साथ)। नैदानिक ​​संदेश का)। कई गलत कार्यक्रम निर्माण अपरिभाषित व्यवहार को प्रस्तुत नहीं करते हैं; उनका निदान किया जाना आवश्यक है। ध्यान दें]

लेकिन दुनिया में क्या gccकर सकता है आशावादी इसे एक अनंत लूप में बदलने के लिए कर रहा है? यह पूरी तरह से निराला लगता है। लेकिन शुक्र है gccकि हमें चेतावनी में इसका पता लगाने के लिए एक सुराग देता है:

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

सुराग है Waggressive-loop-optimizations, इसका क्या मतलब है? सौभाग्य से हमारे लिए यह पहली बार नहीं है जब इस अनुकूलन ने इस तरह से कोड को तोड़ा है और हम भाग्यशाली हैं क्योंकि जॉन रेगेहर ने लेख जीसीसी पूर्व 4.8 ब्रेक ब्रोकन स्पेस 2006 बेंचमार्क में एक मामले का दस्तावेजीकरण किया है जो निम्नलिखित कोड दिखाता है:

int d[16];

int SATD (void)
{
  int satd = 0, dd, k;
  for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
  }
  return satd;
}

लेख कहता है:

अपरिभाषित व्यवहार लूप से बाहर निकलने से ठीक पहले d [16] तक पहुंच रहा है। C99 में, यह सरणी के अंत में स्थित किसी एक स्थिति के लिए एक पॉइंटर बनाने के लिए कानूनी है, लेकिन उस पॉइंटर को डिरेल्ड नहीं किया जाना चाहिए।

और बाद में कहता है:

विस्तार से, यहाँ क्या हो रहा है। AC कंपाइलर, d [++ k] को देखने के बाद, यह मानने की अनुमति देता है कि k का संवर्धित मान सरणी सीमा के भीतर है, क्योंकि अन्यथा अपरिभाषित व्यवहार होता है। यहाँ कोड के लिए, जीसीसी अनुमान लगा सकता है कि k 0. 0.5 की सीमा में है। थोड़ी देर बाद, जब जीसीसी के <16 देखता है, तो यह खुद से कहता है: "अहा- यह अभिव्यक्ति हमेशा सच है, इसलिए हमारे पास एक अनंत लूप है।" यहां स्थिति, जहां संकलक एक उपयोगी डेटाफ्लो तथ्य का अनुमान लगाने के लिए अच्छी तरह से परिभाषित करने की धारणा का उपयोग करता है,

इसलिए कंपाइलर को कुछ मामलों में क्या करना चाहिए क्योंकि यह माना जाता है कि हस्ताक्षरित पूर्णांक अतिप्रवाह अपरिभाषित व्यवहार है तो iहमेशा से कम होना चाहिए 4और इस प्रकार हमारे पास एक अनंत लूप है।

वह बताते हैं कि यह कुख्यात लिनक्स कर्नेल नल पॉइंटर चेक निष्कासन के समान है जहाँ इस कोड को देखना है:

struct foo *s = ...;
int x = s->f;
if (!s) return ERROR;

gccअनुमान लगाया गया है कि चूंकि एक अशक्त सूचक को निष्क्रिय करने के बाद से अंदर भेजा sगया था s->f;और अपरिभाषित व्यवहार sनहीं किया जाना चाहिए, इसलिए यह अशक्त नहीं होना चाहिए और इसलिए if (!s)अगली पंक्ति में चेक का अनुकूलन करता है ।

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


7
मैं समझता हूं कि यह वही है जो कंपाइलर लेखक कर रहा है (मैं कंपाइलर और यहां तक ​​कि एक ऑप्टिमाइज़र या दो भी लिखता था), लेकिन ऐसे व्यवहार हैं जो "उपयोगी" हैं, भले ही वे "अपरिभाषित" हों, और यह मार्च कभी भी अधिक आक्रामक अनुकूलन की ओर जाता है सिर्फ पागलपन है। आपके द्वारा ऊपर दिया गया निर्माण गलत है, लेकिन त्रुटि जांच का अनुकूलन उपयोगकर्ता-शत्रुतापूर्ण है।
ऑस्टिन

1
@ जुलिएनस्टाइन मैं मानता हूं कि यह बहुत आश्चर्यजनक व्यवहार है, यह कहते हुए कि डेवलपर्स को अपरिभाषित व्यवहार से बचने की आवश्यकता है वास्तव में केवल आधा मुद्दा है। स्पष्ट रूप से कंपाइलर को डेवलपर को बेहतर प्रतिक्रिया देने की भी आवश्यकता है। इस मामले में एक चेतावनी का उत्पादन किया जाता है, हालांकि यह वास्तव में पर्याप्त जानकारीपूर्ण नहीं है।
शफीक याघमोर

3
मुझे लगता है कि यह एक अच्छी बात है, मुझे बेहतर, तेज कोड चाहिए। यूबी का उपयोग कभी नहीं किया जाना चाहिए।
पल्म

1
@paulm नैतिक रूप से UB स्पष्ट रूप से खराब है, लेकिन उत्पादन अनुप्रयोगों को प्रभावित करने से पहले डेवलपर्स को UB और अन्य समस्याओं को पकड़ने में मदद करने के लिए क्लैंग स्टैटिक विश्लेषक जैसे बेहतर उपकरण प्रदान करना कठिन है ।
शफीक यघमौर

1
@ शफीक्यगहमौर, यदि आपका डेवलपर चेतावनी को नजरअंदाज कर रहा है, तो क्या संभावना है कि वे उत्पादन पर कोई ध्यान नहीं देंगे? इस मुद्दे को एक आक्रामक "कोई अन्यायपूर्ण चेतावनी" नीति द्वारा आसानी से पकड़ा जा सकता है। क्लैंग उचित है लेकिन आवश्यक नहीं है।
डेवॉर्ड

24

tl; dr कोड एक परीक्षण बनाता है जो पूर्णांक + धनात्मक पूर्णांक == ऋणात्मक पूर्णांक बनाता है । आमतौर पर ऑप्टिमाइज़र इसे बाहर का अनुकूलन नहीं करता है, लेकिन std::endlअगले उपयोग किए जाने के विशिष्ट मामले में , कंपाइलर इस परीक्षण का अनुकूलन करता है। मुझे endlअभी तक पता नहीं चला कि क्या खास है।


-O1 और उच्च स्तरों पर विधानसभा कोड से, यह स्पष्ट है कि gcc लूप को रिफ्लेक्टर करता है:

i = 0;
do {
    cout << i << endl;
    i += NUMBER;
} 
while (i != NUMBER * 4)

सबसे बड़ा मूल्य जो सही ढंग से काम करता है 715827882, वह है मंजिल ( INT_MAX/3)। असेंबली स्निपेट इस प्रकार -O1है:

L4:
movsbl  %al, %eax
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
addl    $715827882, %esi
cmpl    $-1431655768, %esi
jne L6
    // fallthrough to "return" code

ध्यान दें, -1431655768है 4 * 7158278822 के पूरक में।

हिटिंग -O2निम्नलिखित के लिए अनुकूलन करती है:

L4:
movsbl  %al, %eax
addl    $715827882, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655768, %esi
jne L6
leal    -8(%ebp), %esp
jne L6 
   // fallthrough to "return" code

इसलिए जो अनुकूलन किया गया है, वह केवल इतना ही है कि इसे addlऊपर ले जाया गया।

यदि हम 715827883इसके बजाय फिर से जोड़ते हैं तो -O1 संस्करण परिवर्तित संख्या और परीक्षण मूल्य से अलग है। हालाँकि, -O2 तब परिवर्तन करता है:

L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2

जहां cmpl $-1431655764, %esiपर था -O1, उस लाइन को हटा दिया गया है -O2। अनुकूलक का फैसला किया है चाहिए कि जोड़ने 715827883के लिए %esiबराबर कभी नहीं हो सकता -1431655764

यह बहुत हैरान करने वाला है। जोड़ा जा रहा है कि करने के लिए INT_MIN+1 करता है अपेक्षित परिणाम उत्पन्न करते हैं, तो अनुकूलक कि फैसला किया है चाहिए %esiकभी नहीं हो सकता INT_MIN+1और मुझे यकीन है कि क्यों यह है कि तय करेगा नहीं हूँ।

काम करने के उदाहरण में ऐसा लगता है कि यह निष्कर्ष निकालना समान रूप से मान्य होगा कि 715827882एक संख्या को जोड़ना बराबर नहीं हो सकता है INT_MIN + 715827882 - 2! (यह केवल तभी संभव है जब रैपराउंड वास्तव में होता है), फिर भी यह उस उदाहरण में लाइन का अनुकूलन नहीं करता है।


मेरे द्वारा उपयोग किया जा रहा कोड है:

#include <iostream>
#include <cstdio>

int main()
{
    for (int i = 0; i < 4; ++i)
    {
        //volatile int j = i*715827883;
        volatile int j = i*715827882;
        printf("%d\n", j);

        std::endl(std::cout);
    }
}

यदि std::endl(std::cout)हटा दिया जाता है तो अनुकूलन अब नहीं होता है। वास्तव में इसे प्रतिस्थापित करने के std::cout.put('\n'); std::flush(std::cout);कारण अनुकूलन भी नहीं होता है, भले ही std::endlइनबिल्ट हो।

std::endlलूप संरचना के पहले भाग को प्रभावित करने के लिए इनलाइनिंग लगती है (जो मुझे समझ में नहीं आ रहा है कि यह क्या कर रही है, लेकिन मैं इसे यहाँ पोस्ट करूँगा अगर कोई और करता है):

मूल कोड और -O2:

L2:
movl    %esi, 28(%esp)
movl    28(%esp), %eax
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    __ZSt4cout, %eax
movl    -12(%eax), %eax
movl    __ZSt4cout+124(%eax), %ebx
testl   %ebx, %ebx
je  L10
cmpb    $0, 28(%ebx)
je  L3
movzbl  39(%ebx), %eax
L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2                  // no test

की mymanual इनलाइनिंग के साथ std::endl, -O2:

L3:
movl    %ebx, 28(%esp)
movl    28(%esp), %eax
addl    $715827883, %ebx
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    $10, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    $__ZSt4cout, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655764, %ebx
jne L3
xorl    %eax, %eax

इन दोनों में एक अंतर यह है कि %esiमूल में उपयोग किया जाता है, और %ebxदूसरे संस्करण में; क्या सामान्य रूप से %esiऔर बीच %ebxमें परिभाषित शब्दार्थ में कोई अंतर है ? (मैं x86 विधानसभा के बारे में ज्यादा नहीं जानता)।


यह पता लगाना अच्छा होगा कि ऑप्टिमाइज़र का तर्क क्या था हालांकि, यह मेरे लिए इस स्तर पर स्पष्ट नहीं है कि कुछ मामलों में परीक्षण को क्यों अनुकूलित किया गया है और कुछ नहीं
एमएम

8

इस त्रुटि का एक और उदाहरण gcc में बताया जा रहा है जब आपके पास एक लूप होता है जो निरंतर संख्या में पुनरावृत्तियों के लिए निष्पादित होता है, लेकिन आप काउंटर चर का उपयोग एक अनुक्रमणिका के रूप में एक सरणी में करते हैं जिसमें उस आइटम की संख्या से कम है, जैसे:

int a[50], x;

for( i=0; i < 1000; i++) x = a[i];

संकलक यह निर्धारित कर सकता है कि यह लूप सरणी 'ए' के ​​बाहर मेमोरी तक पहुंचने की कोशिश करेगा। संकलक इसके बजाय इसके बारे में शिकायत करता है बल्कि गुप्त संदेश:

पुनरावृति xxu अपरिभाषित व्यवहार को आमंत्रित करता है [-वार = आक्रामक-पाश-अनुकूलन]


इससे भी अधिक गूढ़ बात यह है कि अनुकूलन के चालू होने पर ही संदेश निकलता है। M $ VB संदेश "बाउंड से बाहर निकलना" डमी के लिए है?
रवि गणेश

6

मुझे जो नहीं मिल रहा है वह यह है कि मैं उस अतिप्रवाह ऑपरेशन से क्यों टूट गया हूं?

ऐसा लगता है कि पूर्णांक अतिप्रवाह 4 वें पुनरावृत्ति (के लिए i = 3) में होता है। signedपूर्णांक अतिप्रवाह अपरिभाषित व्यवहार को आमंत्रित करता है । इस मामले में कुछ भी भविष्यवाणी नहीं की जा सकती है। लूप केवल 4बार पुनरावृत्त हो सकता है या यह अनंत या कुछ और हो सकता है!
परिणाम संकलक के लिए या एक ही संकलक के विभिन्न संस्करणों के लिए संकलक भिन्न हो सकते हैं।

C11: 1.3.24 अपरिभाषित व्यवहार:

वह व्यवहार जिसके लिए यह अंतर्राष्ट्रीय मानक कोई आवश्यकता नहीं
रखता है [नोट: यह अंतर्राष्ट्रीय मानक व्यवहार की किसी भी स्पष्ट परिभाषा को छोड़ देता है या जब कोई प्रोग्राम गलत निर्माण या गलत डेटा का उपयोग करता है, तो अपरिभाषित व्यवहार की उम्मीद की जा सकती है। अनुज्ञेय अपरिभाषित व्यवहार की स्थिति की अनदेखी से लेकर अप्रत्याशित परिणामों तक होती है, अनुवाद या कार्यक्रम निष्पादन के दौरान व्यवहार करने के लिए पर्यावरण की विशेषता का एक दस्तावेज तरीके से (नैदानिक ​​संदेश जारी करने के साथ या बिना), अनुवाद या निष्पादन को समाप्त करने के लिए (जारी करने के साथ) नैदानिक ​​संदेश का) । कई गलत कार्यक्रम निर्माण अपरिभाषित व्यवहार को संलग्न नहीं करते हैं; उनका निदान किया जाना आवश्यक है। ध्यान दें]


@bits_international; हाँ।
haccks

4
आप सही कह रहे हैं, यह स्पष्ट करना उचित है कि मैंने क्यों अपमानित किया। इस उत्तर में जानकारी सही है, लेकिन यह शैक्षिक नहीं है और यह कमरे में हाथी को पूरी तरह से अनदेखा करता है: टूटना जाहिरा तौर पर एक अलग जगह पर होता है (स्थिति को रोकना) अतिप्रवाह का कारण बनता है। इस विशिष्ट मामले में चीजों को कैसे तोड़ा जाता है, इसके बारे में नहीं बताया गया है, भले ही यह इस सवाल का मूल है। यह एक खराब शिक्षक की स्थिति है जहाँ शिक्षक का जवाब न केवल समस्या के मूल को संबोधित करता है, यह आगे के प्रश्नों को हतोत्साहित करता है। यह लगभग ऐसा ही लगता है ...
Szabolcs

5
"मैं देख रहा हूं कि यह अपरिभाषित व्यवहार है, और इस बिंदु से मुझे परवाह नहीं है कि यह कैसे या क्यों टूट जाता है। इसे तोड़ने के लिए मानक है। आगे कोई प्रश्न नहीं है।" आप इसे इस तरह से मतलब नहीं हो सकता है, लेकिन यह ऐसा लगता है। मैं एसओ पर इस (दुर्भाग्यपूर्ण सामान्य) रवैये के कम देखने की उम्मीद कर रहा हूं। यह व्यावहारिक रूप से उपयोगी नहीं है। यदि आपको उपयोगकर्ता इनपुट मिलता है, तो हर एक हस्ताक्षरित पूर्णांक ऑपरेशन के बाद अतिप्रवाह के लिए जांच करना उचित नहीं है , भले ही मानक कहता है कि कार्यक्रम का कोई अन्य हिस्सा इसके कारण उड़ा सकता है। यह समझना कि यह कैसे टूटता है अभ्यास में इस तरह की समस्याओं से बचने में मदद करता है
ज़ाबोलक्स

2
@Szabolcs: सी को दो भाषाओं के रूप में सोचना सबसे अच्छा हो सकता है, जिनमें से एक को सरल संकलक को प्रोग्रामर की सहायता से यथोचित-कुशल निष्पादन योग्य कोड प्राप्त करने की अनुमति देने के लिए डिज़ाइन किया गया था, जो उन निर्माणों का फायदा उठाते हैं जो उनके लक्षित लक्ष्य पर विश्वसनीय होंगे, लेकिन नहीं अन्य, और इसके परिणामस्वरूप मानक समिति द्वारा अनदेखी की गई थी, और एक दूसरी भाषा जो ऐसे सभी निर्माणों को शामिल करती है, जिसके लिए मानक को अतिरिक्त अनुकूलन लागू करने के लिए अनुमति देने के उद्देश्य के लिए, अतिरिक्त अनुकूलन को लागू करने की अनुमति नहीं होती है, जो अन्य नागरिकों को देना पड़ सकता है या नहीं। छोड़ दो।
सुपरकैट

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