Swift에서 산술 연산 결과가 오버플로우되면 EXCBAD_INSTRUCTION/EXC_BREAKPOINT 발생

6232 단어 SwiftiOS

문제 코드(Swift1.2)

class TestClass {
    class func test() -> Bool {
        return Int(count() - 1) >= 0
    }

    class func count() -> UInt {
        return 0
    }
}
위에서 말한 바와 같이 실행하면 다음과 같은 곳에서 EXC_BAD_INSTRUCTION (code=EXC_i386_INVOP, subcode=0x0) 실행 중 오류가 발생하고 붕괴됩니다.

이는 시뮬레이터에 의해 수행되기 때문에 i386은 예외이지만 실기(ARM)도 유사EXC_BREAKPOINT (code=1, subcode=0x100011111)의 예외가 발생할 수 있다.

원인을 규명하다


나는 잘 몰라서 어셈블리 단계에서 도대체 무슨 일이 일어났는지 보았다.Xcode의 Debug 메뉴에서 Debug Workflow의 Always Show Disassembly를 선택하십시오.

이렇게 하면 Debuga의 원본 코드는 Swift가 아니라 분리된 어셈블러의 코드로 볼 수 있다.

붕괴된 것은ud2 명령입니다.i386의 명령 집합에 대해 상세하지 않습니다. Wikipedia 에 의하면 명령이 정의되지 않은 것 같습니다.
Instruction
Meaning
Notes
UD2
Undefined
Instruction Generates an invalid opcode. This instruction is provided for software testing to explicitly generate an invalid opcode. The opcode for this instruction is reserved for this purpose.
이상한 일이 생겨서 일부러 실행해서 절차를 정지시켰어요.23줄의ud2는 이전에retq였습니다. 이것은 하위 프로그램의 반환 코드입니다.그럼 어디서 튀어나온 거겠지.자세히 보면 13줄에 점프 명령이 있고ud2의 주소를 가리킨다.
    0x106e18d07 <+39>: jb     0x106e18d25               ; <+69> at TestClass.swift:13
jub은 이전 산술 연산 결과가 음수일 때 만들어진 점프 명령인 Jump If Below 명령입니다.여기서 말한 이전의 산술 연산은 9행이다
    0x106e18cf7 <+23>: subq   $0x1, %rax
그리고 rax 레지스터에서 1을 빼고 있습니다.이 -1의 rax 레지스터 내용은 이전 하위 호출의 호출 결과를 포함합니다.
이것이 바로 Swiftcount() - 1의 부분이라는 것을 이미 알고 계신 것 같습니다.컴파일러가count()-1 결과가 마이너스일 때 오류가 발생하는 기계 언어를 의도적으로 생성한 것이다.count () 가 반환하는 결과는 UInt이기 때문에 기호 정수가 없는 연산 결과가 음수이면 실행 중 오류가 발생합니다.
C언어에 익숙한 사람이라면 언시그널과 시그널은 단순히 숫자만 보는 문제라는 걸 알기 때문에'최종 평가를 하기 전에 정확한 형식으로 출연하는 게 좋다'는 생각을 많이 하는 편이다.상황에 따라 정수형 범위를 적극적으로 넘어설 수 있는 코드도 쓴다.그러나 Swift의 경우 "계산하는 과정에서 unsigned의 유형이 음수로 변하는 것을 허용하지 않는다"고 말했다.이런 입장.
실행할 때의 효율으로 볼 때 매번 산술 연산이 넘쳐나는 것을 검사하는 것은 비용이 매우 크지만 매우 재미있다.

최대치를 넘으면 어떻게 되나요?


여기에 의문이 생겼다.정수형의 최대치를 초과할 때도 실행 중의 오류가 발생합니까?해볼게요.
class TestClass {
    class func test() -> UInt {
        return count() + 1 // ここで EXC_BAD_INSTRUCTION
    }

    class func count() -> UInt {
        return UInt.max
    }
}
예상대로.C 언어의 경우 t, UInt입니다.max에 1을 더하면 0으로 돌아가지만, Swift는 넘침을 허용하지 않습니다.UInt뿐만 아니라 Int.max에 1을 더하거나 Int.min에서 1을 빼도 실행 중 오류가 발생할 수 있습니다.
이 근처에 C 언어에 익숙한 사람은 주의해야 한다.

공식적 방법


이 행동Swift 설명서도 잘 썼다.
Arithmetic operators (+, -, *,/, % and so forth) detect and disallow value overflow, to avoid unexpected results when working with numbers that become larger or smaller than the allowed value range of the type that stores them.
그러니까그 밖에
You can opt in to value overflow behavior by using Swift’s overflow operators, as described in Overflow Operators .
이런 재미있는 기술도 있다.Overflow Operator는 무엇입니까?

Swift의 Overflow Opertor


위 링크에 따라 산술 연산 결과를 Overflow로 설정하려면 Overflow Operator를 명시적으로 사용할 수 있습니다.예컨대 아래의 예.
  • Overflow addition (&+)
  • Overflow subtraction (&-)
  • Overflow multiplication (&*)
  • 재미있다.

    수정 방법


    문제가 발생하지 않기 위해서는 먼저 count()의 값 자체를 인트로 한 다음에 마이너스를 허용해야 한다.
    class TestClass {
        class func test() -> Bool {
            return Int(count()) - 1 >= 0
        }
    
    ※ count()가 Int의 가격 범위에 도달하는 것을 전제로
    물론 오버플로우 Operator를 사용하면 오류가 발생하지 않지만, 비교 연산과 기대는 다르다.
    class TestClass {
        class func test() -> Bool {
            return count() &- 1 >= 0  // 常に true
        }
    
    0 &- 1의 결과는 UInt입니다.맥스이기 때문에 상술한 비교 표현식은 항상 진실이다.이처럼 넘침을 허용하려는 것이 아니라 마이너스로 잘 만들려면 인트에게 배정해야 한다.

    Swift1.1까지의 상황


    실제로 이 문제에 부딪힌 것은 Swift1.2에게 기회를 주는 것이지만 정수형의 가격 범위를 넘어서면 실행할 때 오류가 발생하는 것이 Swift1.2가 되기 전부터 존재했던 방법이다.
    그러나 Swift1.1까지라면 다음 코드는 문제 없이 구축할 수 있고 실행 시 오류가 발생하지 않습니다.
     class SwiftClass {
        class func test() -> Bool {
            return count() - 1 >= 0 // Swift 1.1までは問題ない
        }
    
        class func count() -> UInt {
            return 0
        }
    }
    
    그리고 Swift1.1이면 다음 코드도 정상적으로 실행할 수 있습니다.
        let b:Int = count() - 1    // bには -1が入る
    
    하지만 실행 중 오류가 발생했습니다.
       let c = Int(count() - 1)   // EXC_BAD_INSTRUCTION
    
    이런 행위의 불안정을 혐오하기 때문에 스와프트1.2는 연산 과정에서도 가격 범위를 판정할 수 있다.

    Objective-C의 NSUInteger와의 동거와 분리 정보


    NSAray 의 count 방법 등이 NSUInteger로 돌아갑니다.따라서 주의하지 않으면 예상치 못한 곳에서 unsigned int의 값을 빼서 오버플로우를 일으킬 수 있습니다.
    스위프트에서 볼 수 있는 NSAray의 카운트는 유인트가 아닌 인트다.사과도 사용하기 어려울 것 같지?
    물론 스위프트 원주민 아레이의 카운트 등도 인트여서 일반적으로 사용하면 문제가 없을 것 같다.
    다만, 자체 제작한 프로그램 라이브러리 등에서 코코아를 패러디한 API가 카운트의 유형을 NSUInteger로 적용하면 이 문제에 쉽게 부딪힐 수 있다고 생각한다.주의하십시오.
    솔직히 말하면,count 속성의 유형은 굳이 unsigned를 필요로 할 필요가 없다고 생각합니다.일반적인 처리 시스템에서는 포인터 크기와 Int의 크기가 일치하기 때문에 unsigned int를 사용하지 않으면 저장할 수 없는 길이의 Arry는 불가능하다.
    아마 애플도 그렇게 생각했을 거예요. 스위프트에서 NSAray를 사용하는 경우count의 유형은 Int이죠.

    좋은 웹페이지 즐겨찾기