같은 방법 원시 연산자를 이해하면 +
, -
, *
와 /
C에서 구현, 난에서 다음 코드 발견 흥미로운 대답을 .
// replaces the + operator
int add(int x, int y) {
while(x) {
int t = (x & y) <<1;
y ^= x;
x = t;
}
return y;
}
이 기능은 +
실제로 백그라운드에서 작동 하는 방식을 보여주는 것 같습니다 . 그러나 그것을 이해하기에는 너무 혼란 스럽습니다. 이러한 작업은 오랫동안 컴파일러가 생성 한 어셈블리 지시문을 사용하여 수행한다고 믿었습니다!
+
운영자 가 게시 된 코드로 구현 되었습니까? 가장 구현? 이것이 2의 보완 또는 기타 구현 종속 기능을 활용합니까?
답변
현명하기 위해 C 사양은 방법을 지정하지 않습니다. 추가 구현 .
그러나 현실적으로 +
CPU의 단어 크기보다 작거나 같은 정수 유형 연산자는 CPU에 대한 추가 명령으로 직접 변환되고, 더 큰 정수 유형은 오버플로를 처리하기 위해 추가 비트가있는 여러 추가 명령으로 변환됩니다.
CPU는 내부적으로 로직 회로를 사용하여 추가를 구현하고 루프, 비트 시프트 또는 C가 작동하는 방식과 유사한 것을 사용하지 않습니다.
답변
2 비트를 추가하면 다음과 같은 결과가 나타납니다. (진실 표)
a | b | sum (a^b) | carry bit (a&b) (goes to next)
--+---+-----------+--------------------------------
0 | 0 | 0 | 0
0 | 1 | 1 | 0
1 | 0 | 1 | 0
1 | 1 | 0 | 1
따라서 비트 xor를 수행하면 캐리없이 합계를 얻을 수 있습니다. 그리고 비트 단위로 수행하면 캐리 비트를 얻을 수 있습니다.
이 관찰을 다중 비트 수에 대해 확장 a
하고b
a+b = sum_without_carry(a, b) + carry_bits(a, b) shifted by 1 bit left
= a^b + ((a&b) << 1)
일단 b
이다 0
:
a+0 = a
따라서 알고리즘은 다음과 같이 요약됩니다.
Add(a, b)
if b == 0
return a;
else
carry_bits = a & b;
sum_bits = a ^ b;
return Add(sum_bits, carry_bits << 1);
재귀를 제거하고 루프로 변환하면
Add(a, b)
while(b != 0) {
carry_bits = a & b;
sum_bits = a ^ b;
a = sum_bits;
b = carrry_bits << 1; // In next loop, add carry bits to a
}
return a;
위의 알고리즘을 염두에두고 코드에서 설명하는 것이 더 간단해야합니다.
int t = (x & y) << 1;
캐리 비트. 두 피연산자의 오른쪽 1 비트가 1 인 경우 캐리 비트는 1입니다.
y ^= x; // x is used now
캐리없는 추가 (캐리 비트 무시 됨)
x = t;
x를 재사용하여 휴대하도록 설정
while(x)
더 많은 캐리 비트가있는 동안 반복
재귀 적 구현 (이해하기 쉬움)은 다음과 같습니다.
int add(int x, int y) {
return (y == 0) ? x : add(x ^ y, (x&y) << 1);
}
이 함수는 +가 실제로 백그라운드에서 어떻게 작동하는지 보여줍니다.
아니요. 일반적으로 (거의 항상) 정수 추가는 기계 명령어 추가로 변환됩니다. 이것은 비트 xor 및 and를 사용하는 대체 구현을 보여줍니다.
답변
이 함수는 +가 실제로 백그라운드에서 어떻게 작동하는지 보여줍니다.
아니요. 이것은 add
실제로 하드웨어 가산기를 사용하는 원시 기계 명령어 로 변환됩니다 .ALU
.
컴퓨터가 어떻게 추가되는지 궁금하다면 여기 기본 가산기가 있습니다.
컴퓨터의 모든 것은 대부분 트랜지스터로 만들어진 논리 게이트를 사용하여 수행됩니다. 전체 가산기에는 반 가산기가 있습니다.
논리 게이트 및 가산기에 대한 기본 자습서는 이를 참조 하십시오 . 비디오는 길지만 매우 유용합니다.
이 비디오에는 기본적인 반가산기가 표시됩니다. 간단한 설명이 필요하면 다음과 같습니다.
반 가산기는 2 비트를 더합니다. 가능한 조합은 다음과 같습니다.
- 0을 더하고 0 = 0
- 1 더하기 0 = 1
- 1 더하기 1 = 10 (이진)
이제 반 가산기는 어떻게 작동합니까? 글쎄요, 그것은 세 개의 논리 게이트 and
,, xor
및 nand
. 은 nand
두 입력이 모두 음수이면 양의 전류를 제공하므로 0과 0의 경우를 해결합니다 xor
. 입력 중 하나는 양수이고 다른 하나는 음수이므로 양의 출력을 제공하므로 문제가 해결됩니다. 1과 0.and
두 입력이 모두 양수인 경우에만 양의 출력을 제공하므로 1과 1의 문제가 해결됩니다. 기본적으로 이제 절반 가산기를 얻었습니다. 그러나 우리는 여전히 비트를 추가 할 수 있습니다.
이제 우리는 전체 가산기를 만듭니다. 완전 가산기는 반가산기를 반복해서 호출하는 것으로 구성됩니다. 이제 캐리가 있습니다. 1과 1을 더하면 캐리 1이됩니다. 따라서 전체 가산기가하는 일은 반가산기에서 캐리를 가져 와서 저장하고 반가산기에 또 다른 인수로 전달하는 것입니다.
캐리를 어떻게 전달할 수 있는지 혼란 스러우면 기본적으로 먼저 하프 가산기를 사용하여 비트를 더한 다음 합계와 캐리를 더합니다. 이제 2 비트로 캐리를 추가했습니다. 따라서 추가해야 할 비트가 끝날 때까지이 작업을 반복하면 결과를 얻을 수 있습니다.
놀랐나요? 이것이 실제로 일어나는 방법입니다. 긴 프로세스처럼 보이지만 컴퓨터는 나노초의 몇 분의 1 또는 좀 더 구체적으로 말하면 절반의 클록 주기로 처리합니다. 때로는 단일 클록 사이클에서도 수행됩니다. 기본적으로 컴퓨터에는 ALU
(의 주요 부분 CPU
), 메모리, 버스 등이 있습니다.
논리 게이트, 메모리 및 ALU에서 컴퓨터 하드웨어를 배우고 컴퓨터를 시뮬레이션하려면이 과정을 볼 수 있습니다.이 과정에서 제가이 모든 것을 배웠습니다. 첫 번째 원칙에서 현대적인 컴퓨터 구축
전자 인증서를 원하지 않는 경우 무료입니다. 코스의 2 부는 올해 봄에 예정되어 있습니다.
답변
C는 추상 기계를 사용하여 C 코드의 기능을 설명합니다. 따라서 작동 방식은 지정되지 않았습니다. 예를 들어 실제로 C를 스크립팅 언어로 컴파일하는 C “컴파일러”가 있습니다.
그러나 대부분의 C 구현에서는 +
기계 정수 크기보다 작은 두 정수 사이에서 어셈블리 명령어로 변환됩니다 (여러 단계 후). 어셈블리 명령어는 기계어 코드로 번역되고 실행 파일에 포함됩니다. 어셈블리는 기계 코드에서 “한 단계 제거 된”언어로, 압축 된 바이너리보다 읽기 쉽도록 고안되었습니다.
그런 다음 해당 기계 코드 (여러 단계 후)는 대상 하드웨어 플랫폼에 의해 해석되며, 여기서 CPU의 명령 디코더에 의해 해석됩니다. 이 명령어 디코더는 명령어를 받아 “제어 라인”을 따라 보내는 신호로 변환합니다. 이러한 신호는 레지스터 및 메모리에서 CPU를 통해 데이터를 라우팅하며, 여기서 값은 종종 산술 논리 장치에서 함께 추가됩니다.
산술 논리 장치는 별도의 가산기와 승수를 갖거나 함께 혼합 할 수 있습니다.
산술 논리 장치에는 덧셈 연산을 수행 한 다음 출력을 생성하는 트랜지스터가 많이 있습니다. 상기 출력은 명령 디코더로부터 생성 된 신호를 통해 라우팅되고 메모리 또는 레지스터에 저장된다.
산술 논리 장치와 명령 디코더 모두에있는 상기 트랜지스터의 레이아웃 (그리고 내가 글로 쓴 부품)은 공장의 칩에 새겨 져 있습니다. 에칭 패턴은 종종 하드웨어 설명 언어를 컴파일하여 생성됩니다. 하드웨어 설명 언어는 무엇에 연결되어 있고 어떻게 작동하는지 추상화하고 트랜지스터와 상호 연결 라인을 생성합니다.
하드웨어 설명 언어에는 시간 ( 연속적 으로 )이 아니라 공간에서 일어나는 일 을 설명하는 시프트 및 루프가 포함될 수 있습니다 . 하드웨어의 다른 부분 간의 연결을 설명합니다. 상기 코드는 위에 게시 한 코드와 매우 유사하게 보일 수 있습니다.
위의 내용은 많은 부품과 레이어에 걸쳐 광택이 있으며 부정확 한 내용이 포함되어 있습니다. 이것은 내 자신의 무능함 (하드웨어와 컴파일러를 모두 작성했지만 둘 다 전문가입니다)과 전체 세부 사항이 SO 게시물이 아닌 경력 한두 번이 필요하기 때문입니다.
다음 은 8 비트 가산기에 대한 SO 게시물입니다. 여기 에 비 SO 게시물이 있습니다. 여기서 일부 가산기 operator+
는 HDL에서 사용 됩니다! (HDL 자체 +
는 저수준 가산기 코드를 이해 하고 생성합니다.)
답변
컴파일 된 C 코드를 실행할 수있는 거의 모든 최신 프로세서에는 정수 추가 기능이 내장되어 있습니다. 게시 한 코드는 정수 추가 연산 코드를 실행하지 않고 정수 추가를 수행하는 영리한 방법이지만 정수 추가가 일반적으로 수행되는 방식은 아닙니다. 실제로 함수 연결은 스택 포인터를 조정하기 위해 어떤 형태의 정수 더하기를 사용합니다.
게시 한 코드는 x와 y를 추가 할 때 공통된 비트와 x 또는 y 중 하나에 고유 한 비트로 분해 할 수 있다는 관찰에 의존합니다.
표현식 x & y
(비트 AND)은 x와 y에 공통된 비트를 제공합니다. 표현식 x ^ y
(비트 배타적 OR)은 x 또는 y 중 하나에 고유 한 비트를 제공합니다.
합계 x + y
는 공통된 비트의 두 배 (x와 y가 모두 해당 비트에 기여하기 때문에)와 x 또는 y에 고유 한 비트의 합계로 다시 쓸 수 있습니다.
(x & y) << 1
공통된 비트의 두 배입니다 (왼쪽 이동에 1이 효과적으로 곱 해짐).
x ^ y
x 또는 y 중 하나에 고유 한 비트입니다.
따라서 x를 첫 번째 값으로, y를 두 번째 값으로 바꾸면 합계는 변경되지 않아야합니다. 첫 번째 값은 비트 덧셈의 전달로, 두 번째 값은 비트 덧셈의 하위 비트로 생각할 수 있습니다.
이 프로세스는 x가 0이 될 때까지 계속되며,이 지점에서 y는 합계를 보유합니다.
답변
당신이 찾은 코드는 아주 원시적 인 컴퓨터 하드웨어 가 “add”명령을 어떻게 구현할 수 있는지 설명하려고합니다 . 이 방법이 어떤 CPU 에서도 사용되지 않는다는 것을 보장 할 수 있기 때문에 “might”라고 말하고 그 이유를 설명하겠습니다.
정상적인 생활에서는 십진수를 사용하고이를 더하는 방법을 배웠습니다. 두 개의 숫자를 더하려면 가장 낮은 두 자리를 더합니다. 결과가 10 미만이면 결과를 기록하고 다음 자리로 진행합니다. 결과가 10 이상이면 결과를 빼고 10을 적고 다음 숫자로 진행하여 1을 더 추가하는 것을 잊지 마십시오. 예 : 23 + 37, 3 + 7 = 10을 더하고 0을 적고 다음 위치에 1을 더 추가하는 것을 잊지 마십시오. 10 초 위치에 (2 + 3) + 1 = 6을 더하고 적어 둡니다. 결과는 60입니다.
이진수로 똑같은 일을 할 수 있습니다. 차이점은 유일한 자릿수는 0과 1이므로 가능한 합계는 0, 1, 2 뿐이라는 것입니다. 32 비트 숫자의 경우 한 자릿수 위치를 차례로 처리합니다. 이것이 바로 원시 컴퓨터 하드웨어가 그렇게하는 방법입니다.
이 코드는 다르게 작동합니다. 두 자릿수가 1이면 두 이진수의 합이 2라는 것을 알고 있습니다. 두 자릿수가 1이면 다음 이진수 위치에 1을 더하고 0을 기록합니다. 이것이 t의 계산이하는 일입니다. 여기서 두 이진수는 모두 1 (&)이고 다음 자리 위치 (<< 1)로 이동합니다. 그런 다음 더하기를 수행합니다. 0 + 0 = 0, 0 + 1 = 1, 1 + 0 = 1, 1 + 1은 2이지만 0을 적습니다. 이것이 배타적 또는 연산자가하는 일입니다.
그러나 다음 자리에서 처리해야하는 모든 1은 처리되지 않았습니다. 여전히 추가해야합니다. 이것이 코드가 루프를 수행하는 이유입니다. 다음 반복에서 모든 추가 1이 추가됩니다.
프로세서가 그렇게하지 않는 이유는 무엇입니까? 루프이기 때문에 프로세서는 루프를 좋아하지 않으며 느립니다. 최악의 경우 32 회 반복이 필요하기 때문에 속도가 느립니다. 0xffffffff (32 개의 1 비트)에 1을 더하면 첫 번째 반복은 y의 비트 0을 지우고 x를 2로 설정합니다. 두 번째 반복은 비트 1을 지 웁니다. y의 x를 4로 설정합니다. 결과를 얻으려면 32 번의 반복이 필요합니다. 그러나 각 반복은 x 및 y의 모든 비트를 처리해야하므로 많은 하드웨어가 필요합니다.
원시 프로세서는 가장 낮은 위치에서 가장 높은 위치까지 십진수 산술을 수행하는 방식만큼 빠르게 작업을 수행합니다. 또한 32 단계가 필요하지만 각 단계는 이전 비트 위치에서 2 비트와 1 개의 값만 처리하므로 구현하기가 훨씬 쉽습니다. 원시 컴퓨터에서도 루프를 구현하지 않고도이를 수행 할 수 있습니다.
현대적이고 빠르고 복잡한 CPU는 “조건부 합산 기”를 사용합니다. 특히 64 비트 가산기와 같이 비트 수가 많으면 많은 시간을 절약 할 수 있습니다.
64 비트 가산기는 두 부분으로 구성됩니다. 첫째, 가장 낮은 32 비트에 대한 32 비트 가산기입니다. 이 32 비트 가산기는 합계와 “carry”(다음 비트 위치에 1을 추가해야한다는 표시기)를 생성합니다. 둘째, 더 높은 32 비트를위한 두 개의 32 비트 가산기 : 하나는 x + y를 더하고 다른 하나는 x + y + 1을 더합니다. 세 가산기는 모두 병렬로 작동합니다. 그런 다음 첫 번째 가산기가 캐리를 생성하면 CPU는 x + y 또는 x + y + 1 두 결과 중 올바른 결과를 선택하고 완전한 결과를 얻습니다. 따라서 64 비트 가산기는 32 비트 가산기보다 두 배가 아닌 아주 조금만 더 걸립니다.
32 비트 가산기 부분은 여러 16 비트 가산기를 사용하여 조건부 합계 가산기로 다시 구현되고 16 비트 가산기는 조건부 합계 가산기입니다.
답변
내 질문은 : + 연산자가 MOST 구현에 게시 된 코드로 구현 되었습니까?
실제 질문에 답해 봅시다. 모든 연산자는 일부 변환 후에 결국 코드로 변환되는 일부 내부 데이터 구조로 컴파일러에 의해 구현됩니다. 실제 컴파일러가 개별 명령문에 대한 코드를 생성하는 경우는 거의 없기 때문에 단일 추가로 생성되는 코드를 말할 수 없습니다.
컴파일러는 실제 작업이 표준에 따라 수행 된 것처럼 동작하는 한 모든 코드를 자유롭게 생성 할 수 있습니다. 그러나 실제로 일어나는 일은 완전히 다를 수 있습니다.
간단한 예 :
static int
foo(int a, int b)
{
return a + b;
}
[...]
int a = foo(1, 17);
int b = foo(x, x);
some_other_function(a, b);
여기에서 추가 지침을 생성 할 필요가 없습니다. 컴파일러가 이것을 다음과 같이 번역하는 것은 완벽하게 합법적입니다.
some_other_function(18, x * 2);
또는 컴파일러는 함수를 foo
연속으로 몇 번 호출하고 간단한 산술이며 이에 대한 벡터 명령을 생성한다는 것을 인식 할 수 있습니다. 또는 추가 결과가 나중에 배열 인덱싱에 lea
사용되고 명령어가 사용됩니다.
연산자는 거의 단독으로 사용되지 않기 때문에 구현 방법에 대해 말할 수 없습니다.
