C 데이터 유형은“대부분의 컴퓨터에서 직접 지원”되는 방식은 무엇입니까?
나는 K & R의 “The C Programming Language” 를 읽고이 문장을 소개했습니다. [Introduction, p. 삼]:
C가 제공하는 데이터 유형과 제어 구조는 대부분의 컴퓨터 에서 직접 지원하기 때문에 자체 포함 된 프로그램을 구현하는 데 필요한 런타임 라이브러리는 매우 작습니다.
굵게 표시된 내용은 무엇을 의미합니까? 컴퓨터에서 직접 지원 하지 않는 데이터 유형 또는 제어 구조의 예가 있습니까?
예, 직접 지원되지 않는 데이터 유형이 있습니다.
많은 임베디드 시스템에는 하드웨어 부동 소수점 장치가 없습니다. 따라서 다음과 같은 코드를 작성할 때
float x = 1.0f, y = 2.0f;
return x + y;
다음과 같이 번역됩니다 :
unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);
그런 다음 컴파일러 또는 표준 라이브러리는의 구현을 제공해야하며 _float_add()
, 이는 임베디드 시스템의 메모리를 차지합니다. 아주 작은 시스템에서 바이트 수를 세는 경우 더해질 수 있습니다.
또 다른 일반적인 예는 64 비트 정수 ( long long
1999 년 이후 C 표준)이며 32 비트 시스템에서 직접 지원되지 않습니다. 이전 SPARC 시스템은 정수 곱셈을 지원하지 않았으므로 런타임에서 곱셈을 제공해야했습니다. 다른 예가 있습니다.
다른 언어
다른 언어에는 더 복잡한 프리미티브가 있습니다.
예를 들어, Lisp 기호는 Lua의 테이블, Python의 문자열, Fortran의 배열 등과 같은 많은 런타임 지원이 필요합니다. C의 동등한 유형은 일반적으로 표준 라이브러리의 일부가 아니거나 (표준 기호 또는 테이블 없음) 훨씬 간단하고 많은 런타임 지원이 필요하지 않습니다 (C의 배열은 기본적으로 포인터 일뿐입니다. 널 종료 문자열은 거의 간단합니다).
제어 구조
C에서 빠진 주목할만한 제어 구조는 예외 처리입니다. 비 로컬 종료는 setjmp()
및 로 제한되어 longjmp()
프로세서 상태의 특정 부분 만 저장하고 복원합니다. 이에 비해 C ++ 런타임은 스택을 걷고 소멸자와 예외 처리기를 호출해야합니다.
실제로, 나는이 소개의 내용이 Kernighan과 Ritchie가 처음으로이 책의 초판에 썼을 때 1978 년 이후로 크게 변하지 않았으며, 당시의 C의 역사와 진화를 현대보다 더 많이 언급하고 있다고 확신합니다. 구현.
컴퓨터는 기본적으로 메모리 뱅크 및 중앙 프로세서이며 각 프로세서는 기계어 코드를 사용하여 작동합니다. 각 프로세서의 디자인 중 일부는 Assembly Language 라고하는 명령어 세트 아키텍처로, 사람이 읽을 수있는 니모닉에서 기계 코드 (일부 숫자)로 일대일로 매핑됩니다.
C 언어의 저자와 그 직전의 B 및 BCPL 언어는 가능한 한 효율적으로 어셈블리로 컴파일 된 언어로 구문을 정의하려는 의도를 가지고 있었지만 실제로는 목표의 한계에 의해 강제되었습니다. 하드웨어. 다른 답변이 지적했듯이, 여기에는 분기 (GOTO 및 C의 다른 흐름 제어), 이동 (할당), 논리 연산 (& | ^), 기본 산술 (더하기, 빼기, 증가, 감소) 및 메모리 주소 지정 (포인터) ). C의 사전 / 사후 증가 및 감소 연산자가 좋은 예입니다. Ken Thompson이 컴파일 한 후 단일 opcode로 직접 변환 할 수 있기 때문에 특히 B 언어에 추가 된 것으로 추정됩니다.
이것이 저자들이 "대부분의 컴퓨터에 의해 직접 지원된다"고 말했을 때의 의미입니다. 다른 언어에는 직접 지원 되지 않는 유형과 구조가 포함 된 것은 아니며 , 설계 상 C 설계가 가장 직접 (때로는 문자 그대로 직접) 어셈블리로 변환 되었음을 의미합니다 .
기본 어셈블리와의 밀접한 관계는 구조화 된 프로그래밍에 필요한 모든 요소를 계속 제공하면서 C의 초기 채택을 이끌어 냈으며 오늘날 코드 컴파일의 효율성이 여전히 중요한 환경에서이 언어를 대중 언어로 유지합니다.
언어의 역사에 대한 흥미로운 글 은 C 언어의 개발-Dennis Ritchie를 참조하십시오 .
짧은 대답은 C가 지원하는 대부분의 언어 구성도 대상 컴퓨터의 마이크로 프로세서에 의해 지원되므로 컴파일 된 C 코드는 마이크로 프로세서의 어셈블리 언어로 매우 훌륭하고 효율적으로 변환되므로 코드가 작고 설치 공간이 줄어 듭니다.
답변이 길수록 약간의 어셈블리 언어 지식이 필요합니다. C에서 다음과 같은 문장 :
int myInt = 10;
어셈블리에서 다음과 같이 번역됩니다.
myInt dw 1
mov myInt,10
이것을 C ++과 비교해보십시오 :
MyClass myClass;
myClass.set_myInt(10);
결과로 생성되는 어셈블리 언어 코드 (큰 MyClass ()의 크기에 따라 다름)는 최대 수백 개의 어셈블리 언어 줄을 추가 할 수 있습니다.
실제로 어셈블리 언어로 프로그램을 작성하지 않으면 순수한 C는 아마도 프로그램을 만들 수있는 "가장 얇고"가장 엄격한 코드 일 것입니다.
편집하다
내 대답에 대한 의견을 감안할 때, 나는 내 정신을 위해 테스트를하기로 결정했습니다. "test.c"라는 프로그램을 만들었습니다.
#include <stdio.h>
void main()
{
int myInt=10;
printf("%d\n", myInt);
}
gcc를 사용하여 이것을 어셈블리로 컴파일했습니다. 다음 명령 줄을 사용하여 컴파일했습니다.
gcc -S -O2 test.c
결과 어셈블리 언어는 다음과 같습니다.
.file "test.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d\n"
.section .text.unlikely,"ax",@progbits
.LCOLDB1:
.section .text.startup,"ax",@progbits
.LHOTB1:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB24:
.cfi_startproc
movl $10, %edx
movl $.LC0, %esi
movl $1, %edi
xorl %eax, %eax
jmp __printf_chk
.cfi_endproc
.LFE24:
.size main, .-main
.section .text.unlikely
.LCOLDE1:
.section .text.startup
.LHOTE1:
.ident "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
.section .note.GNU-stack,"",@progbits
그런 다음 클래스를 정의하고 "test.c"와 동일한 것을 출력하는 "test.cpp"라는 파일을 만듭니다.
#include <iostream>
using namespace std;
class MyClass {
int myVar;
public:
void set_myVar(int);
int get_myVar(void);
};
void MyClass::set_myVar(int val)
{
myVar = val;
}
int MyClass::get_myVar(void)
{
return myVar;
}
int main()
{
MyClass myClass;
myClass.set_myVar(10);
cout << myClass.get_myVar() << endl;
return 0;
}
이 명령을 사용하여 동일한 방식으로 컴파일했습니다.
g++ -O2 -S test.cpp
결과 어셈블리 파일은 다음과 같습니다.
.file "test.cpp"
.section .text.unlikely,"ax",@progbits
.align 2
.LCOLDB0:
.text
.LHOTB0:
.align 2
.p2align 4,,15
.globl _ZN7MyClass9set_myVarEi
.type _ZN7MyClass9set_myVarEi, @function
_ZN7MyClass9set_myVarEi:
.LFB1047:
.cfi_startproc
movl %esi, (%rdi)
ret
.cfi_endproc
.LFE1047:
.size _ZN7MyClass9set_myVarEi, .-_ZN7MyClass9set_myVarEi
.section .text.unlikely
.LCOLDE0:
.text
.LHOTE0:
.section .text.unlikely
.align 2
.LCOLDB1:
.text
.LHOTB1:
.align 2
.p2align 4,,15
.globl _ZN7MyClass9get_myVarEv
.type _ZN7MyClass9get_myVarEv, @function
_ZN7MyClass9get_myVarEv:
.LFB1048:
.cfi_startproc
movl (%rdi), %eax
ret
.cfi_endproc
.LFE1048:
.size _ZN7MyClass9get_myVarEv, .-_ZN7MyClass9get_myVarEv
.section .text.unlikely
.LCOLDE1:
.text
.LHOTE1:
.section .text.unlikely
.LCOLDB2:
.section .text.startup,"ax",@progbits
.LHOTB2:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1049:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $10, %esi
movl $_ZSt4cout, %edi
call _ZNSolsEi
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE1049:
.size main, .-main
.section .text.unlikely
.LCOLDE2:
.section .text.startup
.LHOTE2:
.section .text.unlikely
.LCOLDB3:
.section .text.startup
.LHOTB3:
.p2align 4,,15
.type _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, @function
_GLOBAL__sub_I__ZN7MyClass9set_myVarEi:
.LFB1056:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $_ZStL8__ioinit, %edi
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, %edx
movl $_ZStL8__ioinit, %esi
movl $_ZNSt8ios_base4InitD1Ev, %edi
addq $8, %rsp
.cfi_def_cfa_offset 8
jmp __cxa_atexit
.cfi_endproc
.LFE1056:
.size _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, .-_GLOBAL__sub_I__ZN7MyClass9set_myVarEi
.section .text.unlikely
.LCOLDE3:
.section .text.startup
.LHOTE3:
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I__ZN7MyClass9set_myVarEi
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.hidden __dso_handle
.ident "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
.section .note.GNU-stack,"",@progbits
보다시피 알 수 있듯이 결과 어셈블리 파일은 C ++ 파일보다 훨씬 크고 C 파일에 있습니다. 다른 모든 것들을 잘라 내고 C "main"을 C ++ "main"과 비교하더라도 많은 추가 것들이 있습니다.
K & R은 대부분의 C 표현식 (기술적 의미)이 지원 라이브러리에 대한 함수 호출이 아니라 하나 또는 몇 개의 어셈블리 명령어에 매핑됨을 의미합니다. 일반적인 예외는 하드웨어 div 명령어가없는 아키텍처의 정수 나누기 또는 FPU가없는 컴퓨터의 부동 소수점입니다.
인용문이 있습니다 :
C는 어셈블리 언어의 유연성과 기능을 사용자 친화적 인 어셈블리 언어와 결합합니다.
( 여기서 찾을 수 있습니다 . "조립 언어의 편리함과 표현력을 갖춘 조립 언어의 속도"와 같은 다른 변형을 기억한다고 생각했습니다.)
long int는 일반적으로 기본 시스템 레지스터와 동일한 너비입니다.
일부 고급 언어는 데이터 유형의 정확한 너비를 정의하며 모든 컴퓨터의 구현은 동일하게 작동해야합니다. 그러나 C는 아닙니다.
x86-64에서 128 비트 int로 작업하거나 일반적인 경우 BigInteger (임의의 크기)로 작업하려면 함수 라이브러리가 필요합니다. 모든 CPU는 이제 2의 보수를 음의 정수의 이진 표현으로 사용하지만 C가 설계되었을 때는 그렇지 않았습니다. (그렇기 때문에 비 2s 보완 기계에서 다른 결과를 얻을 수있는 것들이 기술적으로 C 표준에 정의되어 있지 않은 이유입니다.)
데이터 또는 함수에 대한 C 포인터는 어셈블리 주소와 같은 방식으로 작동합니다.
참조 횟수 계산을 원한다면 직접 참조해야합니다. 포인터가 가리키는 객체의 종류에 따라 다른 함수를 호출하는 C ++ 가상 멤버 함수를 원한다면 C ++ 컴파일러는 call
고정 주소를 가진 명령어 이상의 것을 생성해야 합니다.
문자열은 단지 배열입니다
라이브러리 함수 외부에서 제공되는 유일한 문자열 조작은 문자 읽기 / 쓰기입니다. 연결, 하위 문자열, 검색이 없습니다. (문자열은 '\0'
포인터 + 길이가 아닌 8 비트 정수의 null로 끝나는 ( ) 배열 로 저장 되므로 하위 문자열을 얻으려면 원래 문자열에 널을 써야합니다.)
CPU는 때때로 문자열 검색 기능에서 사용하도록 설계된 명령어를 가지고 있지만, 여전히 루프에서 실행 된 명령어 당 1 바이트를 처리합니다. (또는 x86 rep 접두사를 사용합니다. C가 x86으로 디자인 된 경우 문자열 검색 또는 비교는 라이브러리 함수 호출이 아닌 기본 작업 일 수 있습니다.)
다른 많은 답변은 예외 처리, 해시 테이블, 목록과 같이 기본적으로 지원되지 않는 것들의 예를 제공합니다. K & R의 디자인 철학은 C에 기본적으로 이러한 요소가없는 이유입니다.
프로세스의 어셈블리 언어는 일반적으로 점프 (go to), 명령문, 이동 명령문, 이진 관절염 (XOR, NAND 및 AND 등), 메모리 필드 (또는 주소)를 처리합니다. 메모리를 명령과 데이터의 두 가지 유형으로 분류합니다. 그것은 모든 어셈블리 언어에 관한 것입니다 (어셈블리 프로그래머는 그보다 더 많은 것이 있다고 주장하지만 일반적으로 이것으로 요약됩니다). C는이 단순성과 매우 유사합니다.
C는 대수가 산술하는 것을 조립하는 것입니다.
C는 어셈블리의 기본 사항 (프로세서 언어)을 캡슐화합니다. "C가 제공하는 데이터 유형과 제어 구조는 대부분의 컴퓨터에서 직접 지원하기 때문에"보다 진실한 진술 일 것입니다
오해의 소지가있는 비교
- The statement relies on the notion of a "run-time library", which has mostly gone out of fashion since, at least for mainstream high-level languages. (It is still relevant for the smallest embedded systems.) The run-time is the minimal support a program in that language requires to execute when you use only constructs built into the language (as opposed to explicitly calling a function provided by a library).
- In contrast, modern languages tend not to discriminate between the run-time and the standard library, the latter often being quite extensive.
- At the time of the K&R book, C did not even have a standard library. Rather, the available C libraries differed quite a bit between different flavors of Unix.
- For understanding the statement you should not compare to languages with a standard library (such as Lua and Python mentioned in other answers), but to languages with more built-in constructs (such as old-day LISP and old-day FORTRAN mentioned in other answers). Other examples would be BASIC (interactive, like LISP) or PASCAL (compiled, like FORTRAN) which both have (among other things) input/output features built right into the language itself.
- In contrast, there is no standard way to get the computation results out from a C program that is using only the run-time, not any library.
Is there an example of a data type or a control structure that isn't supported directly by a computer?
All the fundamental data types and their operations in the C language can be implemented by one or a few machine-language instructions without looping -- they are directly supported by the (practically every) CPU.
Several popular data types and their operations require dozens of machine-language instructions, or require iterating of some runtime loop, or both.
Many languages have special abbreviated syntax for such types and their operations -- using such data types in C generally requires typing a lot more code.
Such data types, and operations include:
- arbitary-length text string manipulation -- concatenation, substring, assigning a new string to a variable initialized with some other string, etc. ('s = "Hello World!"; s = (s + s)[2:-2]' in Python)
- sets
- objects with nested virtual destructors, as in C++ and every other object-oriented programming language
- 2D matrix multiplication and division; solving linear systems ( "C = B / A; x = A\b" in MATLAB and many array programming languages)
- regular expressions
- variable-length arrays -- in particular, appending an item to the the end of the array, which (sometimes) requires allocating more memory.
- reading the value of variables that change type at runtime -- sometimes it's a float, other times it's a string
- associative arrays (often called "maps" or "dictionaries")
- lists
- ratios ( "(+ 1/3 2/7)" gives "13/21" in Lisp)
- arbitrary-precision arithmetic (often called "bignums")
- converting data into a printable representation (the ".tostring" method in JavaScript)
- saturating fixed-point numbers (often used in embedded C programs)
- evaluating a string typed in at run time as though it were an expression ("eval()" in many programming languages).
All of these operations require dozens of machine-language instructions or require iterating some runtime loop on nearly every processor.
Some popular control structures that also require dozens of machine-language instructions or looping include:
- closures
- continuations
- exceptions
- lazy evaluation
Whether written in C or some other language, when a program manipulates such data types, the CPU must eventually execute whatever instructions are required to manipulate those data types. Those instructions are often contained in a "library". Every programming language, even C, has a "run-time library" for each platform that is included by default in every executable.
Most people who write compilers put the instructions for manipulating all the data types that are "built into the language" into their run-time library. Because C doesn't have any of the above data types and operations and control structures built into the the language, none of them are included in the C run-time library -- which makes the C run-time library smaller than the run-time library of other programming languages that have more of the above stuff built-in to the language.
When a programmer want a program -- in C or any other language of his choice -- to manipulate other data types that are not "built into the language", that programmer generally tells the compiler to include additional libraries with that program, or sometimes (to "avoid dependencies") writes yet another implementation of those operations directly in the program.
What are the built-in data types in C
? They are things like int
, char
, * int
, float
, arrays etc... These data types are understood by the CPU. The CPU knows how to work with arrays, how to dereference pointers and how to perform arithmetic on pointers, integers and floating point numbers.
But when you go to higher level programming languages you have built in abstract datatypes and more complex constructs. For example look at the vast array of built-in classes in the C++ programming language. The CPU doesn't understand classes, objects or abstract datatypes, so the C++ run-time bridges the gap between the CPU and the language. These are examples of datatypes not directly supported by most computers.
It depends on the computer. On the PDP-11, where C was invented, long
was poorly supported (there was an optional add-on module you could buy that supported some, but not all, 32-bit operations). The same is true to various degrees on any 16-bit system, including the original IBM PC. And likewise for 64-bit operations on 32-bit machines or in 32-bit programs, though the C language at the time of the K&R book did not have any 64-bit operations at all. And of course there have been many systems throughout the 80s and 90s [including the 386 and some 486 processors], and even some embedded systems today, that did not directly support floating point arithmetic (float
or double
).
For a more exotic example, some computer architectures only support "word-oriented" pointers (pointing at a two-byte or four-byte integer in memory), and byte pointers (char *
or void *
) had to be implemented by adding an extra offset field. This question goes into some detail about such systems.
The "run-time library" functions it refers to are not the ones you will see in the manual, but functions like these, in a modern compiler's runtime library, which are used to implement the basic type operations that are not supported by the machine. The runtime library that K&R themselves were referring to can be found on The Unix Heritage Society's website - you can see functions like ldiv
(distinct from the C function of the same name, which did not exist at the time) which is used to implement division of 32-bit values, which the PDP-11 did not support even with the add-on, and csv
(and cret
also in csv.c) which save and restore registers on the stack to manage calls and returns from functions.
They were likely also referring to their choice to not support many data types that aren't directly supported by the underlying machine, unlike other contemporary languages such as FORTRAN, which had array semantics that did not map as well to the CPU's underlying pointer support as C's arrays. The fact that C arrays are always zero-indexed and always of known size in all ranks but the first means that there is no need to store the index ranges or sizes of the arrays, and no need to have runtime library functions to access them - the compiler can simply hardcode the necessary pointer arithmetic.
The statement simply means that the data and control structures in C are machine-oriented.
There are two aspects to consider here. One is that the C language has a definition (ISO standard) which allows latitude in how the data types are defined. This means that C language implementations are tailored to the machine. The data types of a C compiler match what is available in the machine which the compiler targets, because the language has latitude for that. If a machine has an unusual word size, like 36 bits, then the type int
or long
can be made to conform to that. Programs which assume that int
is exactly 32 bits will break.
Secondly, because of such portability problems, there is a second effect. In a way, the statement in the K&R has become a sort of self-fulfilling prophesy, or perhaps in reverse. That is to say, implementors of new processors are aware of the keen need for supporting C compilers, and they know that there exists a lot of C code which assumes that "every processor looks like an 80386". Architectures are designed with C in mind: and not only C in mind, but with common misconceptions about C portability in mind also. You simply can't introduce a machine with 9 bit bytes or whatever for general purpose use any more. Programs which assume that the type char
is exactly 8 bits wide will break. Only some programs written by portability experts will continue to work: likely not enough to pull together a complete system with a toolchain, kernel, user space and useful applications, with reasonable effort. In other words, C types look like what is available from the hardware because the hardware was made to look like some other hardware for which many nonportable C programs were written.
Is there an example of a data type or a control structure that isn't supported directly by a computer?
Data types not directly supported in many machine languages: multi-precision integer; linked list; hash table; character string.
Control structures not directly supported in most machine languages: first class continuation; coroutine/thread; generator; exception handling.
All of these require considerable run-time support code created using numerous general purpose instructions, and more elementary data types.
C has some standard data types which are not supported by some machines. Since C99, C has complex numbers. They are made out of two floating-point values and made to work with library routines. Some machines have no floating-point unit at all.
With regard to some data types, it is not clear. If a machine has support for addressing memory using one register as a base address, and another as a scaled displacement, does that mean that arrays are a directly supported data type?
Also, speaking of floating-point, there is standardization: IEEE 754 floating-point. Why your C compiler has a double
which agrees with the floating-point format supported by the processor is not only because the two were made to agree, but because there is an independent standard for that representation.
Things such as
Lists Used in almost all functional languages.
Exceptions.
Associative arrays (Maps) - included in e.g. PHP and Perl.
Garbage collection.
Data types/control structures included in many languages, but not directly supported by the CPU.
Supported directly should be understood as mapping efficiently to the instruction set of the processor.
Direct support for integer types is the rule, except for the long (may require extended arithmetic routines) and short sizes (may require masking).
Direct support for floating-point types requires an FPU to be available.
Direct support for bit fields is exceptional.
Structs and arrays require address computation, directly supported to some extent.
Pointers are always directly supported via indirect addressing.
goto/if/while/for/do are directly supported by unconditional/conditional branches.
switch can be directly supported when a jump table applies.
Function calls are directly supported by means of the stack features.
'IT박스' 카테고리의 다른 글
C #에서 상수 사전 만들기 (0) | 2020.07.23 |
---|---|
쉘 스크립트에 도움말 메소드를 추가하려면 어떻게해야합니까? (0) | 2020.07.23 |
단위 테스트, NUnit 또는 Visual Studio? (0) | 2020.07.23 |
'git gui'를 실행할 때“Loose Object”팝업을 건너 뛰는 방법 (0) | 2020.07.23 |
install : install-file로 추가 된 로컬 메이븐 저장소에서 jar 파일을 제거하는 방법은 무엇입니까? (0) | 2020.07.23 |