bash 셸의 산술 오버플로 등을 경고하지 않는 이유는 무엇입니까?


9

bash쉘 의 산술 평가 기능에 대한 한계가 설정되어 있습니다. 이 매뉴얼은 쉘 산술 의이 측면에 대해 간결하지만 상태는 다음과 같습니다.

오버플로를 확인하지 않고 고정 폭 정수로 평가를 수행하지만 0으로 나누기가 트래핑되어 오류로 표시됩니다. 연산자와 그 우선 순위, 연관성 및 값은 C 언어와 동일합니다.

이것이 말하는 고정 너비 정수는 실제로 어떤 데이터 유형 이 사용 되는지에 관한 것입니다 (그리고 이것이 이것을 초과하는 이유에 대한 세부 사항). 한계 값은 다음 /usr/include/limits.h과 같은 방식으로 표현됩니다 :

#  if __WORDSIZE == 64
#   define ULONG_MAX     18446744073709551615UL
#  ifdef __USE_ISOC99
#  define LLONG_MAX       9223372036854775807LL
#  define ULLONG_MAX    18446744073709551615ULL

그리고 일단 당신이 그것을 알면, 당신은 이렇게 사실을 확인할 수 있습니다 :

# getconf -a | grep 'long'
LONG_BIT                           64
ULONG_MAX                          18446744073709551615

이것은 64 비트 정수 이며 산술 평가와 관련하여 쉘에서 직접 변환됩니다.

# echo $(((2**63)-1)); echo $((2**63)); echo $(((2**63)+1)); echo $((2**64))
9223372036854775807        //the practical usable limit for your everyday use
-9223372036854775808       //you're that much "away" from 2^64
-9223372036854775807     
0
# echo $((9223372036854775808+9223372036854775807))
-1

그래서 둘 사이에 63 2 64 -1, 당신은 얼마나 당신이 ULONG_MAX에서 떨어져을 보여주는 부정적인 정수를 얻을 1 . 평가가 한계에 도달하고 순서에 상관없이 경고가 표시되지 않고 평가의 일부가 0으로 재설정되어 오른쪽 연관 지수 와 같은 비정상적인 동작이 발생할 수 있습니다 .

echo $((6**6**6))                      0   // 6^46656 overflows to 0
echo $((6**6**6**6))                   1   // 6^(6^46656) = 6^0 = 1
echo $((6**6**6**6**6))                6   // 6^(6(6^46656)) = 6^(6^0) = 6^1
echo $((6**6**6**6**6**6))         46656   // 6^(6^(6^(6^46656))) = 6^6
echo $((6**6**6**6**6**6**6))          0   // = 6^6^6^1 = 0
...

를 사용하면 sh -c 'command'아무것도 변경되지 않으므로 이것이 정상적이고 호환되는 출력이라고 가정해야합니다. 식 범위와 한계에 대한 기본적이지만 구체적인 이해와 표현식 평가를 위해 쉘에서 의미하는 바를 이해 했으므로 Linux의 다른 소프트웨어가 사용하는 데이터 유형을 빠르게 볼 수 있다고 생각했습니다. bash이 명령의 입력을 보완하기 위해 몇 가지 소스를 사용했습니다 .

{ shopt -s globstar; for i in /path/to/source_bash-4.2/include/**/*.h /usr/include/**/*.h; do grep -HE '\b(([UL])|(UL)|())LONG|\bFLOAT|\bDOUBLE|\bINT' $i; done; } | grep -iE 'bash.*max'

bash-4.2/include/typemax.h:#    define LLONG_MAX   TYPE_MAXIMUM(long long int)
bash-4.2/include/typemax.h:#    define ULLONG_MAX  TYPE_MAXIMUM(unsigned long long int)
bash-4.2/include/typemax.h:#    define INT_MAX     TYPE_MAXIMUM(int)

if문에 더 많은 출력이 있으며 awk등 의 명령을 검색 할 수 있습니다. 사용한 정규 표현식이 bc및과 같은 임의의 정밀 도구에 대해 아무것도 포착하지 못합니다 dc.


질문

  1. awk산술 평가가 오버플로 될 때 경고하지 않는 이유는 무엇입니까 ( 2 ^ 1024를 평가할 때 와 같음 )? 2 63 에서 2 64 -1 사이의 음의 정수가 왜 무언가를 평가할 때 최종 사용자에게 노출됩니까?
  2. 유닉스의 맛이 대화식으로 ULONG_MAX를 변경할 수 있다는 것을 읽었습니다. 아무도 들어 본 적이 있습니까?
  3. 누군가가 부호없는 정수 최대 값을 임의로 변경 limits.h한 다음 다시 컴파일 bash하면 어떻게 될까요?

노트

1. 나는 매우 간단한 경험적인 것들이기 때문에 내가 본 것을 더 명확하게 설명하고 싶었다. 내가 알아 차린 것은 :

  • (a) <2 ^ 63-1을 제공하는 모든 평가는 정확합니다
  • (b) => 2 ^ 63에서 최대 2 ^ 64까지의 평가는 음의 정수를 나타냅니다.
    • 해당 정수의 범위는 x에서 y입니다. x = -9223372036854775808 및 y = 0입니다.

이를 고려하면 (b)와 같은 평가는 2 ^ 63-1 + x..y 내의 어떤 것으로 표현할 수 있습니다. 예를 들어 문자 그대로 (2 ^ 63-1) +100 002 ((a)보다 작은 숫자 일 수 있음)를 평가하라는 요청을 받으면 -9223372036854675807이됩니다. 나는 단지 내가 추측 한 명백한 것을 진술하고 있지만 이것은 또한 다음 두 가지 표현을 의미합니다.

  • (2 ^ 63-1) + 100002 AND;
  • (2 ^ 63-1) + (LLONG_MAX-{쉘이 ((2 ^ 63-1) + 100 002)에 대해 -9223372036854675807}을 제공하는 것) 우리가 가진 양수 값을 사용하여 잘;
    • (2 ^ 63-1) + (9223372036854775807-9223372036854675807 = 100 000)
    • = 9223372036854775807 + 100 000

실제로 매우 가깝습니다. 두 번째 표현은 (2 ^ 63-1) + 100 002, 즉 우리가 평가하고있는 것 외에 "2"입니다. 이것은 내가 2 ^ 64에서 얼마나 떨어져 있는지 보여주는 음의 정수를 얻는다는 것을 의미합니다. 음의 정수와 한계에 대한 지식으로 bash 쉘의 x..y 범위 내에서 평가를 완료 할 수는 없지만 다른 곳에서는 가능합니다. 데이터는 그런 의미에서 최대 2 ^ 64까지 사용할 수 있습니다 (추가 가능 종이에 올리거나 bc에서 사용하십시오). 그러나 Q에서 아래에 설명 된 것처럼 한계에 도달하면 동작은 6 ^ 6 ^ 6의 동작과 비슷합니다.


5
내 생각 엔 그 이론적 근거는 "쉘이 수학에 적합한 도구는 아니다"로 요약된다. 그것은 그것을 위해 설계된 것이 아니며 당신이 보여줄 때 그것을 우아하게 다루려고 시도하지 않습니다. 지옥, 대부분의 껍질은 수레를 다루지 않습니다!
terdon

@terdon이 경우 쉘이 숫자를 다루는 방식은 내가 들어 본 모든 고급 언어와 정확히 동일합니다. 정수 유형은 고정 크기이며 오버플로 될 수 있습니다.
goldilocks

@terdon 실제로, 6 ^ 6 ^ 6 타이밍 이후로 이것을 연구하면서 QQ는 그것을 깨달았습니다. 또한 많은 콘텐츠를 찾을 수없는 이유는 이것이 C 또는 C99와 관련이 있기 때문이라고 생각했습니다. 저는 개발자 나 IT 담당자가 아니기 때문에 이러한 가정을 배경으로하는 모든 지식을 숙지해야합니다. 분명히 임의의 정밀도를 요구하는 사람은 데이터 유형에 대해 알고 있지만 분명히 그 사람은 아닙니다 :) !).

1
쉘에서 큰 숫자로 작업하려면 다음과 같이 사용 bc하십시오 $num=$(echo 6^6^6 | bc). 불행히도 bc줄 바꿈을하기 때문에 num=$(echo $num | sed 's/\\\s//g')나중에 해야합니다 . 파이프에서 작업하면 실제로 줄 바꿈 문자가 있으며 sed에는 어색하지만 num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')작동합니다. 두 경우 모두 이제 사용할 수있는 정수가 있습니다 (예 :) num2=$(echo "$num * 2" | bc).
goldilocks

1
... 누군가 여기가 당신이 줄 바꿈 기능을 해제 할 수 있습니다 지적 bc설정에 의해 BC_LINE_LENGTH=0.
goldilocks

답변:


11

따라서 2 ^ 63과 2 ^ 64-1 사이에서 ULONG_MAX에서 얼마나 멀리 떨어져 있는지 보여주는 음의 정수가 나타납니다.

아니, 어떻게 알아? 자신의 예에 따르면 최대 값은 다음과 같습니다.

> max=$((2**63 - 1)); echo $max
9223372036854775807

"overflow"가 "ULONG_MAX에서 얼마나 멀리 떨어져 있는지를 나타내는 음의 정수를 얻는다"를 의미한다면, 그에 1을 더하면 -1을 얻지 않아야합니까? 그러나 대신 :

> echo $(($max + 1))
-9223372036854775808

아마도 이것은 다음과 $max같은 이유로 마이너스 차이를 얻기 위해 추가 할 수있는 숫자라는 것을 의미합니다 .

> echo $(($max + 1 + $max))
-1

그러나 이것은 실제로 계속 사실이 아닙니다.

> echo $(($max + 2 + $max))
0

시스템은 2의 보수 를 사용하여 부호있는 정수를 구현 하기 때문 입니다. 1 오버플로 인한 값 은 차이, 음의 차이 등을 제공하려는 시도가 아닙니다. 문자 그대로 값을 제한된 수의 비트로 자른 다음 2의 보수 부호있는 정수로 해석 한 결과입니다. . 예를 들어, $(($max + 1 + $max))-1로 나오는 이유 는 2의 보수에서 가장 높은 값 이 가장 높은 비트를 제외한 모든 비트 (음수를 나타냄) 이기 때문입니다 . 이것들을 함께 추가한다는 것은 기본적으로 모든 비트를 왼쪽으로 옮기는 것을 의미하므로 (크기가 64 비트가 아닌 16 비트 인 경우) :

11111111 11111110

하이 (기호) 비트는 이제 더하기에 이월되므로 설정됩니다. 여기에 (00000000 00000001)을 하나 더 추가하면 모든 비트가 설정 되고 2의 보수는 -1입니다.

나는 첫 번째 질문의 후반부에 부분적으로 대답한다고 생각합니다. "왜 음수 정수가 최종 사용자에게 노출됩니까?" 첫째, 64 비트 2의 보수 수 규칙에 따라 올바른 값이기 때문입니다. 이것은 대부분의 (다른) 범용 고수준 프로그래밍 언어의 관행입니다 (나는 이것을하지 않는 언어는 생각할 수 없습니다). 따라서 bash규칙을 준수하고 있습니다. 첫 번째 질문의 첫 번째 부분 인 "이론은 무엇입니까?"에 대한 답이기도합니다. 프로그래밍 언어 사양의 표준입니다.

두 번째 질문 인 WRT에서는 대화식으로 ULONG_MAX를 변경하는 시스템에 대해 들어 보지 못했습니다.

누군가가 서명되지 않은 정수의 최대 값을 limits.h에서 임의로 변경하면 bash를 다시 컴파일하면 어떻게 될까요?

시스템을 구성하는 데 사용되는 임의의 값이 아니기 때문에 산술 방식에 아무런 영향을 미치지 않습니다. 하드웨어를 반영하는 불변 상수를 저장하는 편리한 값입니다. 비유하면, c 를 55mph로 재정의 할 수 있지만 빛의 속도는 여전히 초당 186,000 마일입니다. c 는 유니버스를 구성하는 데 사용되는 숫자가 아닙니다. 유니버스의 특성에 대한 추론입니다.

ULONG_MAX는 정확히 동일합니다. N 비트 수의 특성에 따라 추론 / 계산됩니다. 상수를 시스템의 현실을 나타내는 것으로 가정하고 어딘가에 사용 하면 변경 limits.h하는 것은 매우 나쁜 생각 입니다.

그리고 당신은 당신의 하드웨어가 부과하는 현실을 바꿀 수 없습니다.


나는 이것이 bash기본 C 라이브러리에 의존하고 표준 C는 그것을 보장하지 않기 때문에 이것이 실제로 (정수 표현의 수단) 보장된다고 생각하지 않습니다. 그러나 이것이 가장 일반적인 최신 컴퓨터에서 사용되는 것입니다.


정말 고마워요! 방에있는 코끼리와 생각을하기 위해 오는 것. 네, 첫 번째 부분은 주로 단어에 관한 것입니다. 내가 의미하는 바를 보여주기 위해 Q를 업데이트했습니다. 왜 2의 보수가 내가 본 것의 일부를 설명하는지 그리고 당신의 대답이 그것을 이해하는 데 귀중한 이유를 연구 할 것입니다! 유닉스 Q에 관한 한, 여기 AIX 에서 ARG_MAX에 대해 잘못 읽었을 것 입니다. 건배!

1
사실 당신이 값을 결정하기 위해 2의 보수를 사용할 수 있습니다 당신이 확실 당신>의 범위에서 2 *입니다 $max당신이 설명하는대로. 내 요점은 1) 목적이 아닙니다 .2) 그렇게하고 싶다면 이해해야합니다 .3) 적용 가능성이 매우 제한되어 있기 때문에 유용하지 않습니다 .4) 각주에 따라 실제로 시스템이 보장하지는 않습니다. 2의 보수를 사용하십시오. 간단히 말해서, 프로그램 코드에서이를 악용하려고 시도하는 것은 매우 나쁜 습관으로 간주됩니다. "큰 숫자"라이브러리 / 모듈이 있습니다 (POSIX, 셸의 bc경우). 필요한 경우이를 사용하십시오.
goldilocks

최근에 나는 단지 2의 보수를 활용하여 빠른 캐리 IC를 가진 4 비트 이진 가산기로 ALU를 구현 한 것을 보았다. 심지어 자신의 보수와의 비교도 있었다 (어떻게 떨어져 있는지를보기 위해). 당신의 설명은 내가 여기에서 본 것을 그 비디오 에서 논의 된 것과 이름을 짓고 연결시킬 수있게하는 데 중요한 역할을했으며 , 일단 모든 문제가 발생하면 그 의미를 파악할 수있게되었습니다. 다시 한 번 감사드립니다! 건배!
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.