C 스타일 언어의 논리 NOT 연산자가“~~”가 아닌“!”인 이유는 무엇입니까?


39

이진 연산자의 경우 비트 연산자와 논리 연산자가 모두 있습니다.

& bitwise AND
| bitwise OR

&& logical AND
|| logical OR

NOT (단항 연산자)은 다르게 동작합니다. 비트와 ~가 있습니다! 논리적입니다.

나는 NOT이 AND와 OR에 반대되는 단항 연산이 아니라는 것을 알고 있지만, 디자이너가 single이 bitwise이고 double이 논리적이라는 원칙에서 벗어나고 다른 성격을 띠는 이유를 생각할 수 없다. 항상 피연산자 값을 반환하는 이중 비트 연산과 같이 잘못 읽을 수 있다고 생각합니다. 그러나 그것은 나에게 진짜 문제가되지 않습니다.

내가 빠진 이유가 있습니까?


7
만약에 !! 논리적 인 의미는 아니고 42를 1로 바꾸는 방법은 무엇입니까? :)
candied_orange

9
겠습니까 ~~당신이 논리 연산자는 비트 연산자의 두 배라는 패턴을 따르는 경우에 다음, NOT 논리에 일관성이 아닐?
Bart van Ingen Schenau

9
첫째, 일관성을 유지하기 위해서는 ~와 ~~이되었을 것입니다. 논리에는 단락이 없습니다.
Christophe

3
기본적인 사용 이유는 일반적인 사용 사례에서 시각적 선명도와 구별이라고 생각합니다. 이항 (즉, 두 피연산자) 연산자는 접두사 (공백으로 구분되는 경향이있는 반면) 단항 연산자는 접두사 (이격되지 않는 경향이 있음)입니다.
스티브

7
일부 의견이 이미에 (그리고 따라하고 싶지 않은 사람들을 위해 언급했듯이 이 링크를 , !!foo? 일반적이지 (A-흔히되지 않음) 관용구입니다 그것은에 제로 또는-제로가 아닌 인수를 정상화. 01.
키이스 톰슨

답변:


108

이상하게도 C 스타일 프로그래밍 언어의 역사는 C로 시작하지 않습니다.

데니스 리치 (Dennis Ritchie)는 이 기사 에서 C의 탄생에 따른 어려움을 잘 설명하고있다 .

읽을 때 C는 이전 BCPL , 특히 연산자 에서 언어 디자인의 일부를 상속 받았다는 것이 분명해집니다 . 상기 문서의 섹션 "신생아 C는"BCPL의 방법을 설명 &하고이 |두 개의 새로운 사업자 풍부하고 &&||. 그 이유는 다음과 같습니다.

  • 와 함께 사용하기 때문에 다른 우선 순위가 필요했습니다 ==
  • 다른 평가 로직 : 왼쪽에서 오른쪽으로 평가 단락 (즉 때 a이다 false에서 a&&b, b평가되지 않음).

흥미롭게도,이 두 배가되는 것은 독자에게 모호성을 만들지 않습니다 : a && b로 잘못 해석되지 않습니다 a(&(&b)). 파싱 ​​관점에서 모호성이 없습니다 . lvalue &b이면 의미 b가 있지만, 비트 단위 &는 정수 피연산자가 필요 하지만 논리 AND는 유일한 합리적인 선택입니다.

BCPL은 이미 ~비트 부정에 사용 되었습니다. 따라서 일관성의 관점에서 ~~논리적 의미를 부여 하기 위해 두 배가 될 수 있습니다 . 이후 불행하게도이 매우 모호했을 ~단항 연산자는 다음과 같습니다 ~~b또한 의미 할 수있다 ~(~b)). 이것이 누락 된 부정에 대해 다른 상징을 선택해야하는 이유입니다.


10
파서는 두 가지 상황을 명확하게 할 수 없으므로 언어 ​​디자이너가 그렇게해야합니다.
BobDalgleish

16
@Steve : 실제로 C와 C와 같은 언어에는 이미 많은 비슷한 문제가 있습니다. 때 파서가 보는이 (t)+1의 추가이다 (t)1또는이의 캐스팅이다 +1유형 t? C ++ 디자인은 템플릿을 >>올바르게 포함하는 방법을 해결 해야했습니다. 등등.
Eric Lippert

6
@ user2357112 요점은 토큰 화기가 맹목적 으로 두 개의 토큰이 아닌 &&단일 &&토큰 으로 가져가는 것이 좋다고 생각합니다 &. 왜냐하면 a & (&b)해석이 합리적인 것이기 때문에 인간은 결코 그것을 의미하지 않았을 것입니다. 그것을으로 취급하는 컴파일러 a && b. 두 반면 !(!a)!!a는 컴파일러가 임의의 토큰 수준의 규칙과 모호성을 해결하기위한 좋은 생각, 그래서 인간을 의미하는 가능한 것들입니다.

17
!!쓰기가 가능하고 합리적 일뿐만 아니라 표준 "부울로 변환"관용구입니다.
R ..

4
dan04는 --avs 의 모호성을 언급한다고 생각합니다 -(-a). 두 구문 모두 유효하지만 의미가 다릅니다.
Ruslan

49

디자이너가 싱글이 비트이고 더블이 논리라는 원리에서 벗어나기로 선택한 이유는 생각할 수 없습니다.

그것은 처음에는 원칙이 아닙니다. 일단 당신이 그것을 깨닫는 것이 더 합리적입니다.

&vs 를 생각하는 더 좋은 방법 &&binaryBoolean 이 아닙니다 . 더 좋은 방법은 그들을 간절 하고 게으른 것으로 생각하는 것 입니다. &작업자는 좌측 및 우측을 실행하고, 결과를 연산한다. &&작업자는 좌측을 실행하고, 그 결과를 계산하기 위해 필요한 경우에만 그 우측을 실행한다.

또한 "이진"과 "부울"에 대해 생각하는 대신 실제로 일어나는 일에 대해 생각하십시오. "이진"버전은 단어로 압축 된 부울 배열에서 부울 연산을 수행하는 것 입니다.

함께 정리해 봅시다. 부울 배열 에서 지연 연산을 수행하는 것이 합리적 입니까? 아니요, 먼저 확인할 "왼쪽"이 없기 때문입니다. 먼저 확인해야 할 "왼쪽"32 개가 있습니다. 그래서 우리는 게으른 연산을 하나의 부울 로 제한하고 , 그 중 하나는 "이진"이고 다른 하나는 "부울"이라는 직관이 나오지만 디자인 자체가 아니라 디자인 의 결과 입니다!

그리고 당신이 그렇게 생각할 때, 왜 존재하지 않는지 명확 !!하지 않습니다 ^^. 이 연산자들 중 어느 것도 피연산자 중 하나의 분석을 건너 뛸 수있는 속성이 없습니다. "게으른" not또는 없습니다 xor.

다른 언어는 이것을 더 명확하게합니다. 예를 들어 일부 언어 and는 "열심히" and also를 의미 하지만 "게으른"을 의미합니다. 그리고 다른 언어도 좀 더 명확 있는지 확인 &하고 &&"진"과 "부울"아니다; 예를 들어 C #에서 두 버전 모두 부울을 피연산자로 사용할 수 있습니다.


2
감사합니다. 이것은 나를 위해 진정한 눈을 뜨게합니다. 너무 안타깝게도 두 가지 답변을받을 수 없습니다.
마틴 마트

10
나는 이것이 좋은 생각하는 방법이라고 생각하지 않습니다 &&&. 열망 사이의 차이점 중 하나입니다 동안 &&&, &의 열망 버전에서 완전히 다르게 동작 &&특히 언어, 어디 &&전용 부울 유형 이외의 지원 유형.
user2357112

14
예를 들어 C 및 C ++의 경우와 1 & 2완전히 다른 결과가 나타납니다 1 && 2.
user2357112

7
@ZizyArcher : 위의 의견에서 언급했듯이 boolC 에서 유형 을 생략하기로 한 결정 은 노크 효과가 있습니다. 우리는 모두 필요 !하고 ~하나의 수단이 하나 개의 수단 "단일 부울로 int를 치료"때문에 "부울의 포장 된 배열로 int를 취급을". 별도의 bool 및 int 유형이있는 경우 하나의 연산자 만 가질 수 있습니다. 제 생각에는 더 나은 디자인 일 것입니다. 그러나 우리는 거의 50 년 늦었습니다. C #은 친숙하게이 디자인을 유지합니다.
Eric Lippert

3
@ 스티브 : 대답이 터무니없는 것처럼 보이면 어딘가에 제대로 표현되지 않은 주장을 했으므로 권위의 주장에 의존해서는 안됩니다. 어리석은 일에 대해 더 말할 수 있습니까?
Eric Lippert

21

TL; DR

C 는 다른 언어에서 !and ~연산자를 상속했습니다 . 모두 &&||다른 사람에 의해 년 후 추가되었다.

긴 답변

역사적으로 C는 초기 언어 B에서 개발되었으며, BCPL은 BCPL을 기반으로했으며 CPL은 Algol을 기반으로했습니다.

C ++, Java 및 C #의 증조 인 Algol 은 프로그래머에게 직관적 인 느낌을주기 위해 참과 거짓을 정의했습니다. 본질적인 적분 값과 동일합니다”. 그러나,이 중 하나 단점은 논리적이고 비트하지가 동일한 작업이 될 수 없다는 것입니다 : 현대의 컴퓨터에서 ~0-1이 아닌 1과 동일과 ~1동일 -2보다는 0을 (심지어 일부 육십 세의 메인 프레임에 ~0나타냅니다 - 모든 CPU에서 0 또는 INT_MIN, ~0 != 1C 언어 표준은 수년 동안 그것을 요구했지만, 대부분의 딸 언어는 부호와 크기 또는 보완을 전혀 지원하지 않습니다.)

Algol은 여러 가지 모드를 사용하고 부울 및 정수 모드에서 연산자를 다르게 해석하여이 문제를 해결했습니다. 즉, 비트 연산은 정수 유형에 대한 것이고 논리 연산은 부울 유형에 대한 것입니다.

BCPL은 비트 단위와 논리 형이 아닌 별도의 부울 유형이지만 단일 not연산자 입니다. C의 초기 선구자가 그 작업을 수행 한 방식은 다음과 같습니다.

R 값이 true이면 비트 패턴이 완전히 하나로 구성됩니다. false의 Rvalue는 0입니다.

참고 true = ~ false

rvalue 라는 용어 가 C 계열 언어에서 완전히 다른 의미로 발전한 것을 볼 수 있습니다. 오늘날 C에서는 "개체 표현"이라고합니다.

이 정의는 논리적 및 비트 단위로 동일한 기계 언어 명령어를 사용하지 않도록합니다. C가 해당 경로를 통과 한 경우 전 세계의 헤더 파일에이라고 표시 #define TRUE -1됩니다.

그러나 B 프로그래밍 언어 는 약한 유형이며 부울 또는 부동 소수점 유형이 없습니다. 모든 것이 int그 후임자 C 와 동등 했습니다. 이것은 프로그램이 논리 값으로 참 또는 거짓 이외의 값을 사용할 때 발생하는 상황을 언어가 정의하는 것이 좋습니다. 처음에는 진실한 표현을 "제로와 같지 않음"으로 정의했습니다. 이것은 CPU 제로 플래그가있는 미니 컴퓨터에서 효율적이었습니다.

당시에는 대안이있었습니다. 동일한 CPU에도 음의 플래그가 있었으며 BCPL의 진리 값은 -1 이었으므로 B는 대신 모든 음수를 진실로, 음수가 아닌 숫자를 모두 거짓으로 정의했을 수 있습니다. (이러한 접근 방식에는 하나의 남은 부분이 있습니다. 같은 사람들이 동시에 개발 한 많은 UNIX 시스템 호출은 모든 오류 코드를 음의 정수로 정의합니다. 많은 시스템 호출은 실패시 여러 다른 음수 값 중 하나를 반환합니다.) 감사하십시오 : 그것은 더 나빴을 수 있습니다!

그러나 정의 TRUE1FALSE같은 0B에서의 정체성 것을 의미하지 않습니다 true = ~ false더 이상 개최하고 비트 및 논리적 표현 사이에 명확로 알골을 허용 강한 입력을 떨어졌다. 이를 위해서는 새로운 논리 연산자가 필요했고 디자이너는 !아마도 같지 않음이 이미 있었기 때문에을 선택했습니다 !=. 이는 등호를 통해 세로 막대처럼 보입니다. 그들은 아직 존재하지 않았 &&거나 같은 규칙을 따르지 ||않았습니다.

틀림없이, 그들은 있어야합니다 : &B 의 연산자는 설계된대로 고장났습니다. B와 C의에서는 1 & 2 == FALSE비록 12두 truthy 값이며,도 C는 부분적으로 첨가함으로써 해결할하려고 한 실수 B.의 논리 연산 표현하는 더 직관적 인 방법 없다 &&하고 ||있지만, 당시의 주요 관심사가 있었다 결국 단락이 작동하여 프로그램이 더 빨리 실행됩니다. 이것의 증거는 더 있다는 것을 없다 ^^: 1 ^ 2두 피연산자가 truthy에도 불구하고 truthy 값이지만, 단락 혜택을 누릴 수 없습니다.


4
+1. 나는 이것이이 연산자들의 진화에 대한 꽤 좋은 가이드 투어라고 생각합니다.
스티브

BTW, 부호 / 크기 및 보완 기계는 입력이 이미 부울 처리 된 경우에도 별도의 비트 대 논리 부정이 필요합니다. ~0(모든 비트 세트)는 1의 보수 음수 0 (또는 트랩 표현)입니다. 부호 / 크기 ~0는 최대 크기의 음수입니다.
Peter Cordes

@PeterCordes 당신은 절대적으로 맞습니다. 나는 두 개의 보완 기계가 훨씬 더 중요하기 때문에 집중했습니다. 아마도 각주 가치가 있습니다.
Davislor

내 의견은 충분하다고 생각하지만 괄호 (1의 보수 또는 부호 / 크기로 작동하지 않음)는 좋은 편집 일 것입니다.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.