Các kiểu dữ liệu C “được hầu hết các máy tính hỗ trợ trực tiếp” như thế nào?


114

Tôi đang đọc “Ngôn ngữ lập trình C” của K&R và bắt gặp tuyên bố này [Giới thiệu, tr. 3]:

Bởi vì các kiểu dữ liệu và cấu trúc điều khiển do C cung cấp được hỗ trợ trực tiếp bởi hầu hết các máy tính , nên thư viện thời gian chạy cần thiết để thực hiện các chương trình độc lập là rất nhỏ.

Câu lệnh in đậm nghĩa là gì? Có ví dụ về kiểu dữ liệu hoặc cấu trúc điều khiển không được máy tính hỗ trợ trực tiếp không?


1
Ngày nay, ngôn ngữ C hỗ trợ số học phức tạp, nhưng ban đầu thì không vì máy tính không hỗ trợ trực tiếp số phức dưới dạng kiểu dữ liệu.
Jonathan Leffler

12
Trên thực tế, về mặt lịch sử thì ngược lại: C được thiết kế từ các hoạt động và kiểu phần cứng có sẵn tại thời điểm đó.
Basile Starynkevitch,

2
Hầu hết các máy tính không có hỗ trợ trực tiếp phần cứng cho nổi chữ số thập phân
PlasmaHH

3
@MSalters: Tôi đang cố gắng gợi ý về một số hướng cho câu hỏi "Có ví dụ về kiểu dữ liệu hoặc cấu trúc điều khiển không được máy tính hỗ trợ trực tiếp không?" mà tôi không giải thích được giới hạn K & R
PlasmaHH

11
Làm thế nào đây không phải là bản sao sau hơn 6 năm kể từ khi Stack Overflow ra mắt?
Peter Mortensen,

Câu trả lời:


143

Có, có những loại dữ liệu không được hỗ trợ trực tiếp.

Trên nhiều hệ thống nhúng, không có đơn vị dấu chấm động phần cứng. Vì vậy, khi bạn viết mã như thế này:

float x = 1.0f, y = 2.0f;
return x + y;

Nó được dịch thành một cái gì đó như thế này:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);

Sau đó, trình biên dịch hoặc thư viện tiêu chuẩn phải cung cấp một triển khai _float_add()chiếm bộ nhớ trên hệ thống nhúng của bạn. Nếu bạn đang đếm byte trên một hệ thống thực sự nhỏ, điều này có thể tăng lên.

Một ví dụ phổ biến khác là số nguyên 64 bit ( long longtrong tiêu chuẩn C từ năm 1999), không được hỗ trợ trực tiếp bởi hệ thống 32 bit. Các hệ thống SPARC cũ không hỗ trợ phép nhân số nguyên, vì vậy phép nhân phải được cung cấp bởi thời gian chạy. Có những ví dụ khác.

Những ngôn ngữ khác

Để so sánh, các ngôn ngữ khác có nhiều nguyên thủy phức tạp hơn.

Ví dụ: một biểu tượng Lisp yêu cầu nhiều hỗ trợ thời gian chạy, giống như bảng trong Lua, chuỗi trong Python, mảng trong Fortran, v.v. Các kiểu tương đương trong C thường không phải là một phần của thư viện tiêu chuẩn (không có ký hiệu hoặc bảng tiêu chuẩn) hoặc chúng đơn giản hơn nhiều và không yêu cầu nhiều hỗ trợ thời gian chạy (mảng trong C về cơ bản chỉ là con trỏ, chuỗi kết thúc bằng nul gần như đơn giản).

Cấu trúc điều khiển

Một cấu trúc điều khiển đáng chú ý bị thiếu trong C là xử lý ngoại lệ. Thoát không địa phương được giới hạn ở setjmp()longjmp(), chỉ lưu và khôi phục các phần nhất định của trạng thái bộ xử lý. Để so sánh, thời gian chạy C ++ phải di chuyển ngăn xếp và gọi các trình hủy và trình xử lý ngoại lệ.


2
về cơ bản chỉ là con trỏ ... đúng hơn, về cơ bản chỉ là những phần bộ nhớ thô. Ngay cả khi đó là lựa chọn nitơ, và câu trả lời là tốt.
Deduplicator

2
Bạn có thể tranh luận rằng các chuỗi bị kết thúc bằng null có "hỗ trợ phần cứng" vì trình kết thúc chuỗi phù hợp với hoạt động 'jump if zero' của hầu hết các bộ xử lý và do đó nhanh hơn một chút so với các cách triển khai chuỗi có thể có khác.
Peteris

1
Đã đăng câu trả lời của riêng tôi để mở rộng về cách C được thiết kế để ánh xạ đơn giản sang asm.
Peter Cordes

1
Vui lòng không sử dụng cụm từ "mảng về cơ bản chỉ là con trỏ", nó có thể gây hiểu lầm nghiêm trọng cho người mới bắt đầu như OP. Một cái gì đó dọc theo dòng "mảng được thực hiện trực tiếp bằng cách sử dụng con trỏ ở cấp phần cứng" sẽ tốt hơn IMO.
The Paramagnetic Croissant,

1
@TheParamagneticCroissant: Tôi nghĩ trong bối cảnh này thì nó phù hợp ... sự rõ ràng đi kèm với cái giá phải trả là độ chính xác.
Dietrich Epp

37

Trên thực tế, tôi cá rằng nội dung của phần giới thiệu này không thay đổi nhiều kể từ năm 1978 khi Kernighan và Ritchie lần đầu tiên viết chúng trong Ấn bản đầu tiên của cuốn sách, và chúng đề cập đến lịch sử và sự phát triển của C vào thời điểm đó nhiều hơn là hiện đại. triển khai.

Máy tính về cơ bản chỉ là ngân hàng bộ nhớ và bộ xử lý trung tâm, và mỗi bộ xử lý hoạt động bằng cách sử dụng một mã máy; một phần trong thiết kế của mỗi bộ xử lý là kiến ​​trúc tập lệnh, được gọi là Hợp ngữ , ánh xạ 1-1 từ một tập hợp các kỹ năng ghi nhớ mà con người có thể đọc được sang mã máy, là tất cả các số.

Các tác giả của ngôn ngữ C - và các ngôn ngữ B và BCPL ngay trước nó - đã có ý định xác định các cấu trúc trong ngôn ngữ được biên dịch thành Assembly hiệu quả nhất có thể ... trên thực tế, họ đã bị buộc phải bởi những hạn chế trong mục tiêu phần cứng. Như các câu trả lời khác đã chỉ ra, điều này liên quan đến các nhánh (GOTO và điều khiển luồng khác trong C), di chuyển (gán), phép toán logic (& | ^), số học cơ bản (cộng, trừ, tăng, giảm) và địa chỉ bộ nhớ (con trỏ ). Một ví dụ điển hình là toán tử tăng và giảm trước / sau trong C, được cho là đã được Ken Thompson thêm vào ngôn ngữ B đặc biệt vì chúng có khả năng dịch trực tiếp sang một opcode duy nhất sau khi được biên dịch.

Đây là ý của các tác giả khi họ nói "được hỗ trợ trực tiếp bởi hầu hết các máy tính". Họ không có nghĩa là các ngôn ngữ khác chứa các kiểu và cấu trúc không được hỗ trợ trực tiếp - mà theo thiết kế, các cấu trúc C được dịch trực tiếp nhất (đôi khi trực tiếp theo nghĩa đen ) sang Assembly.

Mối quan hệ chặt chẽ này với Assembly cơ bản, trong khi vẫn cung cấp tất cả các yếu tố cần thiết cho lập trình có cấu trúc, là điều đã dẫn đến việc C sớm được chấp nhận và điều khiến nó trở thành ngôn ngữ phổ biến ngày nay trong môi trường mà hiệu quả của mã được biên dịch vẫn là chìa khóa.

Để có một bài viết thú vị về lịch sử của ngôn ngữ, hãy xem Sự phát triển của ngôn ngữ C - Dennis Ritchie


14

Câu trả lời ngắn gọn là, hầu hết các cấu trúc ngôn ngữ được hỗ trợ bởi C cũng được hỗ trợ bởi bộ vi xử lý của máy tính đích, do đó, mã C được biên dịch sẽ dịch rất độc đáo và hiệu quả sang ngôn ngữ hợp ngữ của bộ vi xử lý, do đó dẫn đến mã nhỏ hơn và dấu chân nhỏ hơn.

Câu trả lời dài hơn yêu cầu một chút kiến ​​thức về hợp ngữ. Trong C, một câu lệnh như sau:

int myInt = 10;

sẽ dịch sang một cái gì đó như thế này trong assembly:

myInt dw 1
mov myInt,10

So sánh cái này với cái gì đó giống như C ++:

MyClass myClass;
myClass.set_myInt(10);

Mã hợp ngữ kết quả (tùy thuộc vào MyClass () lớn như thế nào), có thể thêm tới hàng trăm dòng hợp ngữ.

Nếu không thực sự tạo ra các chương trình bằng hợp ngữ, thì C thuần túy có lẽ là mã "mỏng nhất" và "chặt chẽ nhất" mà bạn có thể tạo một chương trình.

BIÊN TẬP

Với những nhận xét về câu trả lời của mình, tôi quyết định chạy một bài kiểm tra, chỉ vì sự tỉnh táo của bản thân. Tôi đã tạo một chương trình có tên "test.c", trông giống như sau:

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}

Tôi đã biên dịch nó thành assembly bằng gcc. Tôi đã sử dụng dòng lệnh sau để biên dịch nó:

gcc -S -O2 test.c

Đây là hợp ngữ kết quả:

    .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

Sau đó, tôi tạo một tệp có tên "test.cpp" đã xác định một lớp và xuất ra thứ giống như "test.c":

#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;
}

Tôi đã biên dịch nó theo cùng một cách, sử dụng lệnh này:

g++ -O2 -S test.cpp

Đây là tệp lắp ráp kết quả:

    .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

Như bạn có thể thấy rõ ràng, tệp hợp ngữ kết quả lớn hơn nhiều trên tệp C ++ sau đó nằm trên tệp C. Ngay cả khi bạn cắt bỏ tất cả những thứ khác và chỉ so sánh C "main" với C ++ "main", thì vẫn còn rất nhiều thứ bổ sung.


14
"Mã C ++" đó không phải là C ++. Và mã thực như MyClass myClass { 10 }trong C ++ rất có thể được biên dịch thành chính xác cùng một hội. Các trình biên dịch C ++ hiện đại đã loại bỏ hình phạt trừu tượng. Và kết quả là, chúng thường có thể đánh bại các trình biên dịch C. Ví dụ: hình phạt trừu tượng trong C qsortlà có thật, nhưng C ++ std::sortkhông có hình phạt trừu tượng sau khi tối ưu hóa cơ bản.
MSalters

1
Bạn có thể dễ dàng nhìn thấy sử dụng IDA Pro rằng hầu hết C ++ cấu trúc biên dịch xuống để điều tương tự như làm nó bằng tay trong C, nhà thầu và dtors được inlined cho các đối tượng tầm thường, sau đó tối ưu hóa tương lai được áp dụng
paulm

7

K&R có nghĩa là hầu hết các biểu thức C (ý nghĩa kỹ thuật) ánh xạ tới một hoặc một vài lệnh hợp ngữ, không phải là một lệnh gọi hàm tới thư viện hỗ trợ. Các trường hợp ngoại lệ thông thường là chia số nguyên trên các kiến ​​trúc không có lệnh div phần cứng hoặc dấu phẩy động trên các máy không có FPU.

Có một câu trích dẫn:

C kết hợp tính linh hoạt và sức mạnh của hợp ngữ với tính thân thiện với người dùng của hợp ngữ.

( tìm thấy ở đây . Tôi nghĩ rằng tôi đã nhớ một biến thể khác, như "tốc độ của hợp ngữ với sự tiện lợi và tính dễ biểu đạt của hợp ngữ".)

long int thường có cùng chiều rộng với các thanh ghi của máy gốc.

Một số ngôn ngữ cấp cao hơn xác định chiều rộng chính xác của kiểu dữ liệu của chúng và việc triển khai trên tất cả các máy phải hoạt động như nhau. Không phải C, mặc dù.

Nếu bạn muốn làm việc với 128 bit int trên x86-64, hoặc trong trường hợp chung là BigInteger có kích thước tùy ý, bạn cần một thư viện các hàm cho nó. Tất cả các CPU hiện nay đều sử dụng phần bù 2s làm đại diện nhị phân của số nguyên âm, nhưng ngay cả khi đó không phải là trường hợp khi C được thiết kế. (Đó là lý do tại sao một số thứ sẽ cho kết quả khác nhau trên các máy không bổ sung 2s về mặt kỹ thuật không được xác định trong tiêu chuẩn C.)

Con trỏ C đến dữ liệu hoặc đến các hàm hoạt động giống như địa chỉ hợp ngữ.

Nếu bạn muốn các tham chiếu được đếm lại, bạn phải tự làm. Nếu bạn muốn các hàm thành viên ảo c ++ gọi một hàm khác tùy thuộc vào loại đối tượng mà con trỏ của bạn đang trỏ đến, trình biên dịch C ++ phải tạo ra nhiều thứ hơn là chỉ một calllệnh có địa chỉ cố định.

Chuỗi chỉ là mảng

Bên ngoài các hàm thư viện, các hoạt động chuỗi duy nhất được cung cấp là đọc / ghi một ký tự. Không có concat, không có chuỗi con, không có tìm kiếm. (Các chuỗi được lưu trữ dưới dạng nul-end ('\0' mảng ) gồm các số nguyên 8bit, không phải con trỏ + độ dài, vì vậy để có được một chuỗi con, bạn phải viết một nul vào chuỗi ban đầu.)

Đôi khi CPU có các lệnh được thiết kế để sử dụng bởi một hàm tìm kiếm chuỗi, nhưng vẫn thường xử lý một byte cho mỗi lệnh được thực thi, trong một vòng lặp. (hoặc với tiền tố đại diện x86. Có thể nếu C được thiết kế trên x86, tìm kiếm hoặc so sánh chuỗi sẽ là một phép toán gốc, thay vì một lệnh gọi hàm thư viện.)

Nhiều câu trả lời khác đưa ra ví dụ về những thứ không được hỗ trợ nguyên bản, như xử lý ngoại lệ, bảng băm, danh sách. Triết lý thiết kế của K&R là lý do khiến C không có bất kỳ điều gì trong số này.


"K&R có nghĩa là hầu hết các biểu thức C (ý nghĩa kỹ thuật) ánh xạ tới một hoặc một vài lệnh hợp ngữ, không phải là một lệnh gọi hàm tới thư viện hỗ trợ." Đây là một lời giải thích rất trực quan. Cảm ơn.
gwg

1
Tôi vừa xem qua thuật ngữ "ngôn ngữ von Neumann" ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ). Đó chính xác là C là gì.
Peter Cordes

1
Đây chính là lý do tại sao tôi sử dụng C. Nhưng điều khiến tôi ngạc nhiên khi tôi học C là bằng cách cố gắng tạo hiệu quả cho nhiều loại phần cứng, nó đôi khi không hiệu quả và không hiệu quả trên hầu hết các phần cứng hiện đại. Ý tôi là ví dụ: không-hữu-ích-và-tin-cậy-cách-để-phát-hiện-số-nguyên-tràn-trong-c-nhiều-từ-bổ-sung-sử-dụng-mang-cờ .
Z boson

6

Hợp ngữ của một tiến trình thường xử lý các bước nhảy (đi tới), câu lệnh, câu lệnh di chuyển, số học nhị phân (XOR, NAND, AND OR, v.v.), trường bộ nhớ (hoặc địa chỉ). Phân loại bộ nhớ thành hai loại, lệnh và dữ liệu. Đó là về tất cả một ngôn ngữ hợp ngữ (Tôi chắc chắn rằng các lập trình viên hợp ngữ sẽ tranh luận rằng có nhiều điều hơn thế, nhưng nó tóm gọn lại về điều này nói chung). C gần giống với sự đơn giản này.

C là tập hợp những gì đại số là số học.

C đóng gói những điều cơ bản của hợp ngữ (ngôn ngữ của bộ xử lý). Có lẽ là một câu lệnh đúng hơn "Bởi vì các kiểu dữ liệu và cấu trúc điều khiển do C cung cấp được hỗ trợ trực tiếp bởi hầu hết các máy tính"


5

Cẩn thận với các so sánh gây hiểu lầm

  1. Tuyên bố dựa trên khái niệm về "thư viện thời gian chạy" , vốn hầu như đã lỗi thời kể từ đó, ít nhất là đối với các ngôn ngữ cấp cao chính thống. (Nó vẫn phù hợp với các hệ thống nhúng nhỏ nhất.) Thời gian chạy là sự hỗ trợ tối thiểu mà một chương trình bằng ngôn ngữ đó yêu cầu để thực thi khi bạn chỉ sử dụng các cấu trúc được xây dựng trong ngôn ngữ (trái ngược với việc gọi một cách rõ ràng một hàm do thư viện cung cấp) .
  2. Ngược lại, các ngôn ngữ hiện đại có xu hướng không phân biệt thời gian chạy và thư viện chuẩn , sau thường khá rộng rãi.
  3. Vào thời điểm sách K&R, C thậm chí còn không có thư viện tiêu chuẩn . Thay vào đó, các thư viện C có sẵn khác nhau khá nhiều giữa các phiên bản Unix khác nhau.
  4. Để hiểu câu lệnh, bạn không nên so sánh với các ngôn ngữ có thư viện chuẩn (chẳng hạn như Lua và Python được đề cập trong các câu trả lời khác), nhưng với các ngôn ngữ có nhiều cấu trúc tích hợp hơn (chẳng hạn như LISP cũ và FORTRAN ngày xưa được đề cập trong phần khác câu trả lời). Các ví dụ khác sẽ là BASIC (tương tác, như LISP) hoặc PASCAL (biên dịch, như FORTRAN), cả hai đều có (trong số những thứ khác) các tính năng đầu vào / đầu ra được tích hợp ngay trong ngôn ngữ.
  5. Ngược lại, không có cách chuẩn nào để lấy kết quả tính toán từ một chương trình C chỉ sử dụng thời gian chạy, không sử dụng bất kỳ thư viện nào.

Mặt khác, hầu hết các ngôn ngữ hiện đại chạy bên trong môi trường thời gian chạy chuyên dụng cung cấp các tiện ích như thu gom rác.
Nate CK

5

Có ví dụ về kiểu dữ liệu hoặc cấu trúc điều khiển không được máy tính hỗ trợ trực tiếp không?

Tất cả các kiểu dữ liệu cơ bản và hoạt động của chúng trong ngôn ngữ C có thể được thực hiện bởi một hoặc một vài lệnh ngôn ngữ máy mà không cần lặp - chúng được hỗ trợ trực tiếp bởi (thực tế là mọi) CPU.

Một số kiểu dữ liệu phổ biến và các hoạt động của chúng yêu cầu hàng tá lệnh ngôn ngữ máy hoặc yêu cầu lặp lại một số vòng lặp thời gian chạy hoặc cả hai.

Nhiều ngôn ngữ có cú pháp viết tắt đặc biệt cho các kiểu như vậy và hoạt động của chúng - sử dụng các kiểu dữ liệu như vậy trong C thường đòi hỏi phải nhập nhiều mã hơn.

Các kiểu dữ liệu và hoạt động như vậy bao gồm:

  • thao tác với chuỗi văn bản có độ dài tùy ý - nối, chuỗi con, gán một chuỗi mới cho một biến được khởi tạo bằng một số chuỗi khác, v.v. ('s = "Hello World!"; s = (s + s) [2: -2] 'bằng Python)
  • bộ
  • các đối tượng có hàm hủy ảo lồng nhau, như trong C ++ và mọi ngôn ngữ lập trình hướng đối tượng khác
  • Phép nhân và chia ma trận 2D; giải hệ thống tuyến tính ("C = B / A; x = A \ b" trong MATLAB và nhiều ngôn ngữ lập trình mảng)
  • biểu thức chính quy
  • mảng có độ dài thay đổi - cụ thể là việc nối một mục vào cuối mảng, (đôi khi) yêu cầu cấp phát thêm bộ nhớ.
  • đọc giá trị của các biến thay đổi kiểu trong thời gian chạy - đôi khi nó là một float, những lần khác lại là một chuỗi
  • mảng kết hợp (thường được gọi là "bản đồ" hoặc "từ điển")
  • danh sách
  • tỷ lệ ("(+ 1/3 2/7)" cho "13/21" trong Lisp )
  • số học có độ chính xác tùy ý (thường được gọi là "bignums")
  • chuyển đổi dữ liệu thành một biểu diễn có thể in được (phương thức ".tostring" trong JavaScript)
  • bão hòa số điểm cố định (thường được sử dụng trong các chương trình C nhúng)
  • đánh giá một chuỗi được nhập vào lúc chạy như thể nó là một biểu thức ("eval ()" trong nhiều ngôn ngữ lập trình).

Tất cả các hoạt động này đều yêu cầu hàng chục lệnh ngôn ngữ máy hoặc yêu cầu lặp lại một số vòng lặp thời gian chạy trên hầu hết mọi bộ xử lý.

Một số cấu trúc điều khiển phổ biến cũng yêu cầu hàng chục lệnh ngôn ngữ máy hoặc lặp bao gồm:

  • đóng cửa
  • sự liên tục
  • ngoại lệ
  • đánh giá lười biếng

Cho dù được viết bằng ngôn ngữ C hay một số ngôn ngữ khác, khi một chương trình thao tác các kiểu dữ liệu như vậy, CPU cuối cùng phải thực hiện bất kỳ lệnh nào được yêu cầu để thao tác các kiểu dữ liệu đó. Những hướng dẫn đó thường được chứa trong một "thư viện". Mọi ngôn ngữ lập trình, ngay cả C, đều có một "thư viện thời gian chạy" cho mỗi nền tảng được bao gồm theo mặc định trong mọi tệp thực thi.

Hầu hết những người viết trình biên dịch đưa các hướng dẫn thao tác tất cả các kiểu dữ liệu được "tích hợp sẵn trong ngôn ngữ" vào thư viện thời gian chạy của họ. Bởi vì C không có bất kỳ kiểu dữ liệu nào ở trên và các hoạt động cũng như cấu trúc điều khiển được tích hợp trong ngôn ngữ này, không có kiểu nào trong số chúng được đưa vào thư viện thời gian chạy C - điều này làm cho thư viện thời gian chạy C nhỏ hơn thời gian chạy- thư viện thời gian của các ngôn ngữ lập trình khác có nhiều thứ ở trên được tích hợp sẵn cho ngôn ngữ.

Khi một lập trình viên muốn một chương trình - bằng C hoặc bất kỳ ngôn ngữ nào khác mà anh ta chọn - thao tác với các kiểu dữ liệu khác không được "tích hợp sẵn trong ngôn ngữ", lập trình viên đó thường yêu cầu trình biên dịch bao gồm các thư viện bổ sung với chương trình đó, hoặc đôi khi (để "tránh phụ thuộc") viết thêm một cách triển khai khác của các hoạt động đó trực tiếp trong chương trình.


Nếu thực hiện của bạn của Lisp đánh giá lại (+ 1/3 2/7) là 3/21, tôi nghĩ rằng bạn phải có một thực hiện đặc biệt là sáng tạo ...
RobertB

4

Các kiểu dữ liệu tích hợp trong là Cgì? Họ là những thứ như int, char, * int, float, mảng vv ... Những loại dữ liệu được hiểu bởi CPU. CPU biết cách làm việc với mảng, cách lấy con trỏ tham chiếu và cách thực hiện số học trên con trỏ, số nguyên và số dấu phẩy động.

Nhưng khi bạn chuyển sang các ngôn ngữ lập trình cấp cao hơn, bạn đã xây dựng trong các kiểu dữ liệu trừu tượng và các cấu trúc phức tạp hơn. Ví dụ, hãy nhìn vào một loạt các lớp tích hợp trong ngôn ngữ lập trình C ++. CPU không hiểu các lớp, đối tượng hoặc kiểu dữ liệu trừu tượng, vì vậy thời gian chạy C ++ thu hẹp khoảng cách giữa CPU và ngôn ngữ. Đây là những ví dụ về kiểu dữ liệu không được hầu hết các máy tính hỗ trợ trực tiếp.


2
x86 biết làm việc với một số mảng, nhưng không phải tất cả. Đối với kích thước phần tử lớn hoặc bất thường, nó sẽ cần thực hiện phép tính số nguyên để chuyển đổi chỉ số mảng thành độ lệch con trỏ. Và trên các nền tảng khác, điều này luôn cần thiết. Và ý tưởng rằng CPU không hiểu các lớp C ++ thật nực cười. Đó chỉ là phần bù của con trỏ, như cấu trúc C. Bạn không cần thời gian chạy cho việc đó.
MSalters

@MSalters có, nhưng các phương thức thực tế của các lớp thư viện tiêu chuẩn như iostreams, v.v. là các hàm thư viện thay vì được hỗ trợ trực tiếp bởi trình biên dịch. Tuy nhiên, các ngôn ngữ cấp cao hơn mà họ có thể so sánh với nó không phải là C ++, mà là các ngôn ngữ hiện đại như FORTRAN và PL / I.
Ngẫu nhiên832

1
Các lớp C ++ với các hàm thành viên ảo chuyển thành nhiều thứ hơn là chỉ một phần bù vào một cấu trúc.
Peter Cordes

4

Nó phụ thuộc vào máy tính. Trên PDP-11, nơi C được phát minh, longđược hỗ trợ kém (có một mô-đun bổ sung tùy chọn bạn có thể mua hỗ trợ một số, nhưng không phải tất cả, hoạt động 32-bit). Điều này cũng đúng với các mức độ khác nhau trên bất kỳ hệ thống 16 bit nào, bao gồm cả PC IBM ban đầu. Và tương tự như vậy đối với các hoạt động 64-bit trên máy 32-bit hoặc trong các chương trình 32-bit, mặc dù ngôn ngữ C vào thời điểm sách K&R không có bất kỳ hoạt động 64-bit nào. Và tất nhiên, đã có nhiều hệ thống trong suốt những năm 80 và 90 [bao gồm cả bộ xử lý 386 và 486], và thậm chí một số hệ thống nhúng ngày nay, không hỗ trợ trực tiếp số học dấu phẩy động ( floathoặc double).

Đối với một ví dụ kỳ lạ hơn, một số kiến ​​trúc máy tính chỉ hỗ trợ con trỏ "hướng từ" (trỏ vào số nguyên hai byte hoặc bốn byte trong bộ nhớ) và con trỏ byte ( char *hoặc void *) phải được triển khai bằng cách thêm một trường bù bổ sung. Câu hỏi này đi vào một số chi tiết về các hệ thống như vậy.

Các hàm "thư viện thời gian chạy" mà nó đề cập đến không phải là những hàm bạn sẽ thấy trong sách hướng dẫn, mà là những hàm như thế này, trong thư viện thời gian chạy của trình biên dịch hiện đại , được sử dụng để triển khai các thao tác kiểu cơ bản không được máy hỗ trợ. . Thư viện thời gian chạy mà bản thân K&R đề cập đến có thể được tìm thấy trên trang web của Hiệp hội Di sản Unix - bạn có thể thấy các hàm như ldiv(khác với hàm C cùng tên, không tồn tại vào thời điểm đó) được sử dụng để thực hiện phân chia Các giá trị 32-bit, mà PDP-11 không hỗ trợ ngay cả với tiện ích bổ sung và csv(và cretcả trong csv.c) lưu và khôi phục các thanh ghi trên ngăn xếp để quản lý các lệnh gọi và trả về từ các hàm.

Họ cũng có thể đề cập đến lựa chọn của họ để không hỗ trợ nhiều kiểu dữ liệu không được hỗ trợ trực tiếp bởi máy bên dưới, không giống như các ngôn ngữ hiện đại khác như FORTRAN, có ngữ nghĩa mảng không ánh xạ cũng như hỗ trợ con trỏ cơ bản của CPU như Mảng của C. Thực tế là mảng C luôn được lập chỉ mục bằng 0 và luôn có kích thước đã biết trong tất cả các cấp nhưng điều đầu tiên có nghĩa là không cần lưu trữ phạm vi chỉ mục hoặc kích thước của mảng và không cần có các hàm thư viện thời gian chạy để truy cập chúng - trình biên dịch có thể đơn giản mã hóa số học con trỏ cần thiết.


3

Câu lệnh đơn giản có nghĩa là dữ liệu và cấu trúc điều khiển trong C là hướng máy.

Có hai khía cạnh cần xem xét ở đây. Một là ngôn ngữ C có một định nghĩa (tiêu chuẩn ISO) cho phép vĩ độ trong cách xác định các kiểu dữ liệu. Điều này có nghĩa là các triển khai ngôn ngữ C được điều chỉnh cho phù hợp với máy . Các kiểu dữ liệu của trình biên dịch C khớp với những gì có sẵn trong máy mà trình biên dịch nhắm mục tiêu, vì ngôn ngữ có vĩ độ cho điều đó. Nếu một máy có kích thước từ bất thường, chẳng hạn như 36 bit, thì loại inthoặc longcó thể được chế tạo để phù hợp với kích thước đó. Các chương trình giả định rằng intchính xác là 32 bit sẽ bị hỏng.

Thứ hai, vì những vấn đề về tính di động như vậy, có tác động thứ hai. Theo một cách nào đó, tuyên bố trong K&R đã trở thành một kiểu tiên tri tự hoàn thành , hoặc có lẽ ngược lại. Điều đó có nghĩa là, những người triển khai các bộ xử lý mới nhận thức được nhu cầu quan trọng về việc hỗ trợ các trình biên dịch C và họ biết rằng tồn tại rất nhiều mã C giả định rằng "mọi bộ xử lý trông giống như 80386". Kiến trúc được thiết kế với C: và không chỉ có C, mà còn có những quan niệm sai lầm phổ biến về tính di động của C. Bạn chỉ đơn giản là không thể giới thiệu một máy có 9 byte bit hoặc bất cứ thứ gì cho mục đích sử dụng chung nữa. Các chương trình giả định rằng loạicharrộng chính xác là 8 bit sẽ bị phá vỡ. Chỉ một số chương trình được viết bởi các chuyên gia về tính di động sẽ tiếp tục hoạt động: có khả năng không đủ để kết hợp một hệ thống hoàn chỉnh với chuỗi công cụ, nhân, không gian người dùng và các ứng dụng hữu ích, với nỗ lực hợp lý. Nói cách khác, các loại C trông giống như những gì có sẵn từ phần cứng vì phần cứng được tạo ra để trông giống như một số phần cứng khác mà nhiều chương trình C phi di động được viết.

Có ví dụ về kiểu dữ liệu hoặc cấu trúc điều khiển không được máy tính hỗ trợ trực tiếp không?

Các kiểu dữ liệu không được hỗ trợ trực tiếp trong nhiều ngôn ngữ máy: số nguyên đa độ chính xác; danh sách liên kết; bảng băm; chuỗi ký tự.

Các cấu trúc điều khiển không được hỗ trợ trực tiếp trong hầu hết các ngôn ngữ máy: tiếp tục lớp đầu tiên; đăng quang / chủ đề; máy phát điện; xử lý ngoại lệ.

Tất cả những điều này yêu cầu mã hỗ trợ thời gian chạy đáng kể được tạo bằng cách sử dụng nhiều hướng dẫn mục đích chung và nhiều kiểu dữ liệu cơ bản hơn.

C có một số kiểu dữ liệu chuẩn không được một số máy hỗ trợ. Vì C99 nên C có số phức. Chúng được tạo từ hai giá trị dấu phẩy động và được thiết kế để hoạt động với các quy trình thư viện. Một số máy không có đơn vị dấu phẩy động nào cả.

Đối với một số kiểu dữ liệu, nó không rõ ràng. Nếu một máy có hỗ trợ định địa chỉ bộ nhớ bằng cách sử dụng một thanh ghi làm địa chỉ cơ sở và một thanh ghi khác làm địa chỉ cơ sở, điều đó có nghĩa là các mảng là một kiểu dữ liệu được hỗ trợ trực tiếp?

Ngoài ra, nói về dấu phẩy động, có tiêu chuẩn hóa: dấu phẩy động IEEE 754. Tại sao trình biên dịch C của bạn có một doubletiêu chuẩn đồng ý với định dạng dấu phẩy động được hỗ trợ bởi bộ xử lý không chỉ vì cả hai được thực hiện để đồng ý, mà bởi vì có một tiêu chuẩn độc lập cho cách biểu diễn đó.


2

Những thứ như

  • Danh sách Được sử dụng trong hầu hết các ngôn ngữ chức năng.

  • Các trường hợp ngoại lệ .

  • Mảng liên kết (Bản đồ) - bao gồm trong PHP và Perl.

  • Thu gom rác .

  • Các kiểu dữ liệu / cấu trúc điều khiển được bao gồm trong nhiều ngôn ngữ, nhưng không được CPU hỗ trợ trực tiếp.


2

Được hỗ trợ trực tiếp nên được hiểu là ánh xạ hiệu quả tới tập lệnh của bộ xử lý.

  • Hỗ trợ trực tiếp cho các kiểu số nguyên là quy tắc, ngoại trừ kích thước dài (có thể yêu cầu quy trình số học mở rộng) và kích thước ngắn (có thể yêu cầu che).

  • Hỗ trợ trực tiếp cho các kiểu dấu phẩy động yêu cầu phải có FPU.

  • Hỗ trợ trực tiếp cho các trường bit là đặc biệt.

  • Các cấu trúc và mảng yêu cầu tính toán địa chỉ, được hỗ trợ trực tiếp ở một mức độ nào đó.

  • Con trỏ luôn được hỗ trợ trực tiếp thông qua địa chỉ gián tiếp.

  • goto / if / while / for / do được hỗ trợ trực tiếp bởi các chi nhánh có điều kiện / có điều kiện.

  • công tắc có thể được hỗ trợ trực tiếp khi áp dụng bảng nhảy.

  • Các lệnh gọi hàm được hỗ trợ trực tiếp bởi các tính năng ngăn xếp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.