ประเภทข้อมูล C“ ได้รับการสนับสนุนโดยตรงจากคอมพิวเตอร์ส่วนใหญ่” อย่างไร


114

ฉันกำลังอ่าน“ ภาษาการเขียนโปรแกรม C”ของ K & R และเจอข้อความนี้ [Introduction, p. 3]:

เนื่องจากชนิดข้อมูลและโครงสร้างการควบคุมที่จัดเตรียมโดย C ได้รับการสนับสนุนโดยตรงจากคอมพิวเตอร์ส่วนใหญ่ไลบรารีรันไทม์ที่จำเป็นในการติดตั้งโปรแกรมในตัวจึงมีขนาดเล็ก

คำสั่งเป็นตัวหนาหมายถึงอะไร? มีตัวอย่างประเภทข้อมูลหรือโครงสร้างการควบคุมที่คอมพิวเตอร์ไม่รองรับโดยตรงหรือไม่


1
ทุกวันนี้ภาษาซีรองรับการคำนวณทางคณิตศาสตร์ที่ซับซ้อน แต่เดิมนั้นไม่ได้เป็นเพราะคอมพิวเตอร์ไม่รองรับจำนวนเชิงซ้อนเป็นชนิดข้อมูลโดยตรง
Jonathan Leffler

12
จริงๆแล้วมันก็เป็นอีกวิธีหนึ่งในอดีต: C ได้รับการออกแบบจากการทำงานของฮาร์ดแวร์และประเภทที่มีอยู่ในขณะนั้น
Basile Starynkevitch

2
คอมพิวเตอร์ส่วนใหญ่ไม่รองรับฮาร์ดแวร์โดยตรงสำหรับการลอยทศนิยม
PlasmaHH

3
@MSalters: ฉันพยายามบอกใบ้แนวทางบางอย่างสำหรับคำถามที่ว่า "มีตัวอย่างประเภทข้อมูลหรือโครงสร้างการควบคุมที่คอมพิวเตอร์ไม่รองรับโดยตรงหรือไม่" ซึ่งฉันไม่ได้ตีความว่า จำกัด เฉพาะ K&R
PlasmaHH

11
จะไม่ซ้ำกันเกิน 6 ปีหลังจากเปิดตัว Stack Overflow ได้อย่างไร
Peter Mortensen

คำตอบ:


143

ใช่มีประเภทข้อมูลที่ไม่รองรับโดยตรง

ในระบบฝังตัวจำนวนมากไม่มีหน่วยจุดลอยตัวของฮาร์ดแวร์ ดังนั้นเมื่อคุณเขียนโค้ดดังนี้:

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ในมาตรฐาน C ตั้งแต่ปี 2542) ซึ่งระบบ 32 บิตไม่รองรับโดยตรง ระบบ SPARC แบบเก่าไม่รองรับการคูณจำนวนเต็มดังนั้นการคูณจึงต้องจัดทำโดยรันไทม์ ยังมีตัวอย่างอื่น ๆ

ภาษาอื่น ๆ

เมื่อเปรียบเทียบแล้วภาษาอื่น ๆ มีภาษาดั้งเดิมที่ซับซ้อนกว่า

ตัวอย่างเช่นสัญลักษณ์ Lisp ต้องการการสนับสนุนรันไทม์จำนวนมากเช่นเดียวกับตารางใน Lua สตริงใน Python อาร์เรย์ใน Fortran และอื่น ๆ ประเภทที่เทียบเท่าใน C มักจะไม่เป็นส่วนหนึ่งของไลบรารีมาตรฐานเลย (ไม่มีสัญลักษณ์หรือตารางมาตรฐาน) หรือง่ายกว่ามากและไม่ต้องการการสนับสนุนรันไทม์มากนัก (อาร์เรย์ใน C เป็นเพียงตัวชี้เท่านั้นสตริงที่สิ้นสุดด้วย nul คือ เกือบจะง่าย)

โครงสร้างการควบคุม

โครงสร้างการควบคุมที่โดดเด่นที่ขาดหายไปจาก C คือการจัดการข้อยกเว้น Nonlocal exit ถูก จำกัดsetjmp()และlongjmp()เพียงแค่บันทึกและกู้คืนบางส่วนของสถานะโปรเซสเซอร์ โดยการเปรียบเทียบรันไทม์ C ++ ต้องเดินสแต็กและเรียกตัวทำลายและตัวจัดการข้อยกเว้น


2
โดยพื้นฐานแล้วเป็นเพียงตัวชี้ ... แต่โดยพื้นฐานแล้วเป็นเพียงหน่วยความจำดิบ แม้ว่าจะเป็นการเลือกที่ดี แต่คำตอบก็ดีอยู่ดี
Deduplicator

2
คุณสามารถโต้แย้งได้ว่าสตริงที่สิ้นสุดด้วยค่าว่างนั้นมี "การสนับสนุนฮาร์ดแวร์" เนื่องจากตัวยุติสตริงเหมาะกับการดำเนินการ "กระโดดถ้าเป็นศูนย์" ของโปรเซสเซอร์ส่วนใหญ่และเร็วกว่าการใช้งานสตริงอื่น ๆ เล็กน้อย
Peteris

1
โพสต์คำตอบของฉันเองเพื่อขยายความเกี่ยวกับวิธีที่ C ถูกออกแบบมาเพื่อแมปกับ asm
Peter Cordes

1
โปรดอย่าใช้การจัดระเบียบ "อาร์เรย์เป็นเพียงตัวชี้" ซึ่งอาจทำให้ผู้เริ่มต้นเข้าใจผิดอย่าง OP ได้อย่างจริงจัง "อาร์เรย์ถูกนำไปใช้โดยตรงโดยใช้พอยน์เตอร์ในระดับฮาร์ดแวร์" จะดีกว่า IMO
ครัวซองต์ Paramagnetic

1
@TheParamagneticCroissant: ฉันคิดว่าในบริบทนี้มันเหมาะสม ... ความชัดเจนมาพร้อมกับความแม่นยำ
Dietrich Epp

37

จริงๆแล้วฉันจะพนันได้เลยว่าเนื้อหาของบทนำนี้ไม่ได้เปลี่ยนแปลงไปมากนักตั้งแต่ปี 1978 เมื่อ Kernighan และ Ritchie เขียนเป็นครั้งแรกใน First Edition ของหนังสือและพวกเขาอ้างถึงประวัติศาสตร์และวิวัฒนาการของ C ในเวลานั้นมากกว่าสมัยใหม่ การใช้งาน

โดยพื้นฐานแล้วคอมพิวเตอร์เป็นเพียงธนาคารหน่วยความจำและหน่วยประมวลผลกลางและโปรเซสเซอร์แต่ละตัวทำงานโดยใช้รหัสเครื่อง ส่วนหนึ่งของการออกแบบโปรเซสเซอร์แต่ละตัวคือสถาปัตยกรรมชุดคำสั่งที่เรียกว่าภาษาแอสเซมบลีซึ่งแมปแบบหนึ่งต่อหนึ่งจากชุดของหน่วยความจำที่มนุษย์อ่านได้กับรหัสเครื่องซึ่งเป็นตัวเลขทั้งหมด

ผู้เขียนภาษา C - และภาษา B และ BCPL ที่นำหน้ามันทันทีมีเจตนาในการกำหนดโครงสร้างในภาษาที่รวบรวมไว้ใน Assembly ได้อย่างมีประสิทธิภาพมากที่สุดเท่าที่จะเป็นไปได้ ... ในความเป็นจริงพวกเขาถูกบังคับโดยข้อ จำกัด ในเป้าหมาย ฮาร์ดแวร์. ดังที่คำตอบอื่น ๆ ได้ชี้ให้เห็นสิ่งนี้เกี่ยวข้องกับกิ่งก้าน (GOTO และการควบคุมการไหลอื่น ๆ ใน C) การเคลื่อนย้าย (การกำหนด) การดำเนินการเชิงตรรกะ (& | ^) การคำนวณทางคณิตศาสตร์พื้นฐาน (การบวกการลบการเพิ่มการลดลง) และการกำหนดแอดเดรสหน่วยความจำ (พอยน์เตอร์ ) ตัวอย่างที่ดีคือตัวดำเนินการก่อน / หลังการเพิ่มและการลดลงใน C ซึ่ง Ken Thompson ถูกเพิ่มลงในภาษา B โดยเฉพาะเนื่องจากสามารถแปลโดยตรงเป็น opcode เดียวที่รวบรวมได้

นี่คือความหมายของผู้เขียนเมื่อกล่าวว่า "รองรับโดยตรงจากคอมพิวเตอร์ส่วนใหญ่" พวกเขาไม่ได้หมายความว่าภาษาอื่นมีประเภทและโครงสร้างที่ไม่ได้รับการสนับสนุนโดยตรง แต่หมายความว่าด้วยการออกแบบโครงสร้าง C ที่แปลตรงที่สุด (บางครั้งก็ตรงตามตัวอักษร ) ในแอสเซมบลี

ความสัมพันธ์ใกล้ชิดกับแอสเซมบลีพื้นฐานในขณะที่ยังคงให้องค์ประกอบทั้งหมดที่จำเป็นสำหรับการเขียนโปรแกรมแบบมีโครงสร้างเป็นสิ่งที่นำไปสู่การยอมรับในช่วงต้นของ C และสิ่งที่ทำให้ภาษานี้เป็นที่นิยมในสภาพแวดล้อมที่ประสิทธิภาพของการคอมไพล์โค้ดยังคงเป็นกุญแจสำคัญ

สำหรับการเขียนประวัติศาสตร์ของภาษาที่น่าสนใจโปรดดูพัฒนาการของภาษาซี - เดนนิสริตชี่


14

คำตอบสั้น ๆ คือโครงสร้างภาษาส่วนใหญ่ที่รองรับโดย 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.cpp" ซึ่งกำหนดคลาสและเอาท์พุทแบบเดียวกันกับ "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;
}

ฉันรวบรวมมันในลักษณะเดียวกันโดยใช้คำสั่งนี้:

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" กับ "main" ของ C ++ แต่ก็มีสิ่งพิเศษมากมาย


14
"รหัส C ++" นั้นไม่ใช่ C ++ และโค้ดจริงเช่นMyClass myClass { 10 }ใน C ++ มีแนวโน้มที่จะคอมไพล์ให้เป็นแอสเซมบลีเดียวกัน คอมไพเลอร์ C ++ สมัยใหม่ได้กำจัดการลงโทษที่เป็นนามธรรม และด้วยเหตุนี้พวกเขาจึงสามารถเอาชนะคอมไพเลอร์ C ได้บ่อยครั้ง เช่นการลงโทษนามธรรมใน C เป็นqsortเรื่องจริง แต่ C ++ std::sortไม่มีโทษนามธรรมหลังจากการปรับให้เหมาะสมขั้นพื้นฐานแล้ว
MSalters

1
คุณสามารถดูได้อย่างง่ายดายโดยใช้ IDA Pro ว่าโครงสร้าง C ++ ส่วนใหญ่คอมไพล์เป็นสิ่งเดียวกับการทำด้วยตนเองใน C ตัวสร้างและ dtors จะอินไลน์สำหรับวัตถุเล็กน้อยจากนั้นการเพิ่มประสิทธิภาพในอนาคตจะถูกนำไปใช้
Paulm

7

K&R หมายความว่านิพจน์ C ส่วนใหญ่ (ความหมายทางเทคนิค) จับคู่กับคำสั่งประกอบหนึ่งหรือสองคำสั่งไม่ใช่การเรียกใช้ฟังก์ชันไปยังไลบรารีสนับสนุน ข้อยกเว้นตามปกติคือการหารจำนวนเต็มบนสถาปัตยกรรมที่ไม่มีคำสั่ง div ฮาร์ดแวร์หรือทศนิยมบนเครื่องที่ไม่มี FPU

มีใบเสนอราคา:

C รวมความยืดหยุ่นและพลังของภาษาแอสเซมบลีเข้ากับภาษาแอสเซมบลีที่ใช้งานง่าย

( พบที่นี่ฉันคิดว่าฉันจำรูปแบบอื่นได้เช่น "ความเร็วของภาษาแอสเซมบลีพร้อมความสะดวกและการแสดงออกของภาษาแอสเซมบลี")

int ยาวมักจะมีความกว้างเท่ากับการลงทะเบียนเครื่องดั้งเดิม

ภาษาระดับสูงกว่าบางภาษากำหนดความกว้างที่แน่นอนของชนิดข้อมูลและการใช้งานบนเครื่องทั้งหมดจะต้องทำงานเหมือนกัน ไม่ใช่ C แม้ว่า

หากคุณต้องการทำงานกับ 128 บิต ints บน x86-64 หรือในกรณีทั่วไป BigInteger ที่มีขนาดตามอำเภอใจคุณต้องมีไลบรารีของฟังก์ชันสำหรับมัน ตอนนี้ซีพียูทั้งหมดใช้ส่วนเสริม 2s เป็นตัวแทนไบนารีของจำนวนเต็มลบ แต่ถึงอย่างนั้นก็ไม่ได้เป็นเช่นนั้นเมื่อ C ได้รับการออกแบบ (นั่นเป็นเหตุผลว่าทำไมบางสิ่งที่จะให้ผลลัพธ์ที่แตกต่างกันในเครื่องที่ไม่ใช่ 2s-complement นั้นไม่ได้กำหนดไว้ในมาตรฐาน C ทางเทคนิค)

ตัวชี้ไปยังข้อมูลหรือฟังก์ชันทำงานในลักษณะเดียวกับที่อยู่แอสเซมบลี

หากคุณต้องการการอ้างอิงแบบนับการอ้างอิงคุณต้องดำเนินการเอง หากคุณต้องการฟังก์ชันสมาชิกเสมือน c ++ ที่เรียกใช้ฟังก์ชันอื่นขึ้นอยู่กับชนิดของวัตถุที่ตัวชี้ของคุณชี้ไปคอมไพเลอร์ C ++ จะต้องสร้างมากกว่าcallคำสั่งที่มีที่อยู่คงที่

สตริงเป็นเพียงอาร์เรย์

นอกเหนือจากฟังก์ชันไลบรารีการดำเนินการสตริงเดียวที่มีให้คืออ่าน / เขียนอักขระ ไม่มี concat ไม่มีสตริงย่อยไม่มีการค้นหา (สตริงถูกจัดเก็บเป็น'\0'อาร์เรย์nul-terminated ( ) ของจำนวนเต็ม 8 บิตไม่ใช่ตัวชี้ + ความยาวดังนั้นในการรับสตริงย่อยคุณจะต้องเขียน nul ลงในสตริงเดิม)

บางครั้งซีพียูจะมีคำสั่งที่ออกแบบมาเพื่อใช้งานโดยฟังก์ชันการค้นหาสตริง แต่โดยปกติจะประมวลผลทีละไบต์ต่อคำสั่งที่ดำเนินการแบบวนซ้ำ (หรือด้วยคำนำหน้าตัวแทน x86 บางทีถ้า C ได้รับการออกแบบบน x86 การค้นหาสตริงหรือการเปรียบเทียบอาจเป็นการดำเนินการแบบเนทีฟแทนที่จะเป็นการเรียกใช้ฟังก์ชันไลบรารี)

คำตอบอื่น ๆ อีกมากมายเป็นตัวอย่างของสิ่งที่ไม่ได้รับการสนับสนุนโดยกำเนิดเช่นการจัดการข้อยกเว้นตารางแฮชรายการ ปรัชญาการออกแบบของ K & R คือเหตุผลที่ C ไม่มีสิ่งเหล่านี้โดยกำเนิด


"K&R หมายความว่านิพจน์ C ส่วนใหญ่ (ความหมายทางเทคนิค) จับคู่กับคำสั่งประกอบหนึ่งหรือสองคำสั่งไม่ใช่การเรียกใช้ฟังก์ชันไปยังไลบรารีสนับสนุน" นี่เป็นคำอธิบายที่เข้าใจง่ายมาก ขอบคุณ
gwg

1
ฉันเพิ่งเจอคำว่า "ภาษาฟอนนอยมันน์" ( en.wikipedia.org/wiki/Von_Neumann_programming_languages ) C คืออะไร
Peter Cordes

1
นี่เป็นเหตุผลว่าทำไมฉันถึงใช้ C. แต่สิ่งที่ทำให้ฉันประหลาดใจเมื่อฉันเรียนรู้ C ก็คือการพยายามที่จะมีประสิทธิภาพสำหรับฮาร์ดแวร์ที่หลากหลายบางครั้งมันก็ไม่มีประสิทธิภาพและไม่มีประสิทธิภาพกับฮาร์ดแวร์ที่ทันสมัยที่สุด ฉันหมายถึงตัวอย่างไม่มีประโยชน์และมีความน่าเชื่อถือทางไปตรวจสอบจำนวนเต็มล้นในคและหลายคำที่ใช้นอกจากนี้ที่นำติดตัวธง
Z boson

6

ภาษาแอสเซมบลีของกระบวนการโดยทั่วไปจะเกี่ยวข้องกับการกระโดด (ไปที่), คำสั่ง, คำสั่งย้าย, ไบนารีอาร์ริติก (XOR, NAND, AND OR ฯลฯ ) ฟิลด์หน่วยความจำ (หรือที่อยู่) แบ่งหน่วยความจำออกเป็นสองประเภทคำสั่งและข้อมูล นั่นเป็นเรื่องเกี่ยวกับภาษาแอสเซมบลีทั้งหมดคือ (ฉันแน่ใจว่าโปรแกรมเมอร์แอสเซมบลีจะเถียงว่ามีมากกว่านั้น แต่โดยทั่วไปแล้วมันจะเดือดมาก) C ใกล้เคียงกับความเรียบง่ายนี้

C คือการรวบรวมพีชคณิตคือเลขคณิต

C ห่อหุ้มพื้นฐานของแอสเซมบลี (ภาษาของโปรเซสเซอร์) น่าจะเป็นคำสั่งที่แท้จริงมากกว่า "เนื่องจากชนิดข้อมูลและโครงสร้างการควบคุมที่จัดทำโดย C ได้รับการสนับสนุนโดยตรงจากคอมพิวเตอร์ส่วนใหญ่"


5

ระวังการเปรียบเทียบที่ทำให้เข้าใจผิด

  1. คำสั่งขึ้นอยู่กับไฟล์ แนวคิดของ "ไลบรารีรันไทม์"ซึ่งส่วนใหญ่ล้าสมัยไปแล้วอย่างน้อยก็สำหรับภาษาระดับสูงกระแสหลัก (ยังคงมีความเกี่ยวข้องกับระบบฝังตัวที่เล็กที่สุด) เวลาทำงานคือการสนับสนุนขั้นต่ำที่โปรแกรมในภาษานั้นต้องการในการดำเนินการเมื่อคุณใช้โครงสร้างที่มีอยู่ในภาษาเท่านั้น (ตรงข้ามกับการเรียกฟังก์ชันที่จัดเตรียมโดยไลบรารีอย่างชัดเจน) .
  2. ในทางตรงกันข้ามภาษาสมัยใหม่มักจะไม่แบ่งแยกระหว่างเวลาทำงานกับไลบรารีมาตรฐานซึ่งภาษาหลังมักจะค่อนข้างกว้างขวาง
  3. ในช่วงเวลาของหนังสือ K&R C ไม่มีห้องสมุดมาตรฐานด้วยซ้ำด้วยซ้ำ แต่ไลบรารี C ที่มีอยู่นั้นแตกต่างกันเล็กน้อยระหว่างรสชาติที่แตกต่างกันของ Unix
  4. เพื่อความเข้าใจข้อความคุณไม่ควรเปรียบเทียบกับภาษาที่มีไลบรารีมาตรฐาน (เช่น Lua และ Python ที่กล่าวถึงในคำตอบอื่น ๆ ) แต่เป็นภาษาที่มีโครงสร้างในตัวมากกว่า (เช่น LISP แบบเก่าและ FORTRAN แบบเก่าที่กล่าวถึงในอื่น ๆ คำตอบ) ตัวอย่างอื่น ๆ จะเป็น BASIC (โต้ตอบเช่น LISP) หรือ PASCAL (คอมไพล์เช่น FORTRAN) ซึ่งทั้งสองมีคุณลักษณะอินพุต / เอาต์พุต (เหนือสิ่งอื่นใด) ที่สร้างขึ้นในภาษานั้น ๆ
  5. ในทางตรงกันข้ามไม่มีวิธีมาตรฐานในการทำให้ผลลัพธ์การคำนวณออกมาจากโปรแกรม C ที่ใช้เฉพาะรันไทม์ไม่ใช่ไลบรารีใด ๆ

ในทางกลับกันภาษาที่ทันสมัยส่วนใหญ่ทำงานภายในสภาพแวดล้อมรันไทม์เฉพาะที่มีสิ่งอำนวยความสะดวกเช่นการรวบรวมขยะ
Nate CK

5

มีตัวอย่างประเภทข้อมูลหรือโครงสร้างการควบคุมที่คอมพิวเตอร์ไม่รองรับโดยตรงหรือไม่

ประเภทข้อมูลพื้นฐานทั้งหมดและการดำเนินการในภาษา C สามารถใช้งานได้โดยคำสั่งภาษาเครื่องหนึ่งหรือสองสามคำสั่งโดยไม่ต้องวนซ้ำซึ่งได้รับการสนับสนุนโดยตรงจาก CPU (ในทางปฏิบัติ)

ข้อมูลยอดนิยมหลายประเภทและการดำเนินการต้องใช้คำสั่งภาษาเครื่องจำนวนมากหรือต้องการการวนซ้ำของรันไทม์บางส่วนหรือทั้งสองอย่าง

หลายภาษามีไวยากรณ์แบบย่อพิเศษสำหรับประเภทดังกล่าวและการดำเนินการ - การใช้ประเภทข้อมูลดังกล่าวใน C โดยทั่วไปจะต้องพิมพ์โค้ดมากกว่านี้มาก

ประเภทข้อมูลและการดำเนินการดังกล่าวรวมถึง:

  • การจัดการสตริงข้อความที่มีความยาวตามอำเภอใจ - การต่อสายอักขระย่อยการกำหนดสตริงใหม่ให้กับตัวแปรที่เริ่มต้นด้วยสตริงอื่น ๆ เป็นต้น ('s = "Hello World!"; s = (s + s) [2: -2] 'ใน Python)
  • ชุด
  • อ็อบเจ็กต์ที่มีตัวทำลายเสมือนที่ซ้อนกันเช่นเดียวกับใน C ++ และภาษาโปรแกรมเชิงวัตถุอื่น ๆ
  • การคูณและการหารเมทริกซ์ 2 มิติ การแก้ระบบเชิงเส้น ("C = B / A; x = A \ b" ใน MATLAB และภาษาโปรแกรมอาร์เรย์จำนวนมาก)
  • นิพจน์ทั่วไป
  • อาร์เรย์ที่มีความยาวผันแปร - โดยเฉพาะอย่างยิ่งการต่อท้ายรายการที่ส่วนท้ายของอาร์เรย์ซึ่ง (บางครั้ง) ต้องมีการจัดสรรหน่วยความจำเพิ่มเติม
  • การอ่านค่าของตัวแปรที่เปลี่ยนประเภทในขณะรันไทม์ - บางครั้งก็เป็นค่าลอยบางครั้งก็เป็นสตริง
  • อาร์เรย์เชื่อมโยง (มักเรียกว่า "แผนที่" หรือ "พจนานุกรม")
  • รายการ
  • อัตราส่วน ("(+ 1/3 2/7)" ให้ "13/21" ใน Lisp )
  • arbitrary-precision arithmetic (มักเรียกว่า "bignums")
  • การแปลงข้อมูลเป็นการแสดงที่สามารถพิมพ์ได้ (วิธี ".tostring" ใน JavaScript)
  • การอิ่มตัวของตัวเลขจุดตายตัว (มักใช้ในโปรแกรม C แบบฝัง)
  • การประเมินสตริงที่พิมพ์ในขณะทำงานราวกับว่าเป็นนิพจน์ ("eval ()" ในภาษาโปรแกรมต่างๆ)

การดำเนินการทั้งหมดนี้ต้องใช้คำสั่งภาษาเครื่องจำนวนมากหรือต้องการการวนซ้ำรันไทม์ในโปรเซสเซอร์เกือบทุกตัว

โครงสร้างการควบคุมยอดนิยมบางอย่างที่ต้องใช้คำสั่งภาษาเครื่องหรือการวนซ้ำรวมถึง:

  • ปิด
  • ข้อยกเว้น
  • ขี้เกียจประเมิน

ไม่ว่าจะเขียนด้วยภาษา C หรือภาษาอื่นเมื่อโปรแกรมจัดการกับประเภทข้อมูลดังกล่าวในที่สุด CPU จะต้องดำเนินการตามคำสั่งใด ๆ ก็ตามที่จำเป็นเพื่อจัดการกับประเภทข้อมูลเหล่านั้น คำแนะนำเหล่านี้มักมีอยู่ใน "ไลบรารี" ทุกภาษาในการเขียนโปรแกรมแม้กระทั่งภาษา C จะมี "ไลบรารีรันไทม์" สำหรับแต่ละแพลตฟอร์มซึ่งรวมไว้โดยค่าเริ่มต้นในทุกปฏิบัติการ

คนส่วนใหญ่ที่เขียนคอมไพเลอร์จะใส่คำแนะนำในการจัดการชนิดข้อมูลทั้งหมดที่ "สร้างไว้ในภาษา" ลงในไลบรารีรันไทม์ เนื่องจาก C ไม่มีชนิดข้อมูลและการดำเนินการและโครงสร้างการควบคุมใด ๆข้างต้นที่สร้างไว้ในภาษาจึงไม่มีสิ่งเหล่านี้รวมอยู่ในไลบรารีรันไทม์ C ซึ่งทำให้ไลบรารีรันไทม์ C มีขนาดเล็กกว่ารัน - ไลบรารีเวลาของภาษาโปรแกรมอื่น ๆ ที่มีสิ่งต่างๆข้างต้นในตัวภาษา

เมื่อโปรแกรมเมอร์ต้องการโปรแกรม - ในภาษา C หรือภาษาอื่นที่เขาเลือก - เพื่อจัดการกับชนิดข้อมูลอื่น ๆ ที่ไม่ได้ "สร้างไว้ในภาษา" โดยทั่วไปโปรแกรมเมอร์จะบอกให้คอมไพเลอร์รวมไลบรารีเพิ่มเติมเข้ากับโปรแกรมนั้นหรือบางครั้ง (เพื่อ "หลีกเลี่ยงการพึ่งพา") เขียนการดำเนินการอื่น ๆ ของการดำเนินการเหล่านั้นโดยตรงในโปรแกรม


หากการใช้ Lisp ของคุณประเมิน (+ 1/3 2/7) เป็น 3/21 ฉันคิดว่าคุณต้องมีการใช้งานที่สร้างสรรค์เป็นพิเศษ ...
RobertB

4

ชนิดข้อมูลในตัวCคืออะไร? พวกเขาเป็นสิ่งที่ชอบint, char, * int, floatอาร์เรย์ ฯลฯ ... ประเภทข้อมูลเหล่านี้มีความเข้าใจโดย CPU ซีพียูรู้วิธีการทำงานกับอาร์เรย์วิธีหักล้างตัวชี้และวิธีคำนวณเลขคณิตบนตัวชี้จำนวนเต็มและเลขทศนิยม

แต่เมื่อคุณไปที่ภาษาการเขียนโปรแกรมระดับสูงคุณได้สร้างประเภทข้อมูลนามธรรมและโครงสร้างที่ซับซ้อนมากขึ้น ตัวอย่างเช่นดูอาร์เรย์ของคลาสในตัวมากมายในภาษาโปรแกรม C ++ CPU ไม่เข้าใจคลาสออบเจ็กต์หรือประเภทข้อมูลนามธรรมดังนั้นเวลารัน C ++ จึงเชื่อมช่องว่างระหว่าง CPU และภาษา นี่คือตัวอย่างของประเภทข้อมูลที่คอมพิวเตอร์ส่วนใหญ่ไม่รองรับโดยตรง


2
x86 รู้ว่าจะทำงานกับอาร์เรย์บางส่วน แต่ไม่ใช่ทั้งหมด สำหรับองค์ประกอบที่มีขนาดใหญ่หรือผิดปกติจะต้องใช้เลขคณิตจำนวนเต็มเพื่อแปลงดัชนีอาร์เรย์เป็นออฟเซ็ตตัวชี้ และบนแพลตฟอร์มอื่น ๆ สิ่งนี้จำเป็นเสมอ และความคิดที่ว่า CPU ไม่เข้าใจคลาส C ++ เป็นเรื่องที่น่าหัวเราะ นั่นเป็นเพียงการชดเชยตัวชี้เช่นโครงสร้าง C คุณไม่จำเป็นต้องใช้รันไทม์สำหรับสิ่งนั้น
MSalters

@MSalters ใช่ แต่วิธีการจริงของคลาสไลบรารีมาตรฐานเช่น iostreams ฯลฯ เป็นฟังก์ชันไลบรารีแทนที่จะได้รับการสนับสนุนโดยตรงจากคอมไพเลอร์ อย่างไรก็ตามภาษาระดับสูงกว่าที่พวกเขามีแนวโน้มที่จะเปรียบเทียบกับมันไม่ใช่ภาษา C ++ แต่เป็นภาษาร่วมสมัยเช่น FORTRAN และ PL / I
Random832

1
คลาส C ++ พร้อมฟังก์ชั่นสมาชิกเสมือนแปลเป็นมากกว่าการชดเชยเป็นโครงสร้าง
Peter Cordes

4

มันขึ้นอยู่กับคอมพิวเตอร์ บน PDP-11 ที่ C ถูกคิดค้นขึ้นlongได้รับการสนับสนุนไม่ดี (มีโมดูลเสริมเสริมที่คุณสามารถซื้อได้ซึ่งรองรับการทำงานแบบ 32 บิตบางส่วน แต่ไม่ใช่ทั้งหมด) เช่นเดียวกับองศาต่างๆในระบบ 16 บิตรวมถึง IBM PC ดั้งเดิม และเช่นเดียวกันสำหรับการดำเนินการ 64 บิตบนเครื่อง 32 บิตหรือในโปรแกรม 32 บิตแม้ว่าภาษา C ในช่วงเวลาของหนังสือ K&R จะไม่มีการดำเนินการ 64 บิตเลยก็ตาม และแน่นอนว่ามีระบบมากมายตลอดช่วงทศวรรษที่ 80 และ 90 [รวมถึงโปรเซสเซอร์ 386 และ 486 บางตัว] และแม้แต่ระบบฝังตัวบางระบบในปัจจุบันที่ไม่ได้รองรับการคำนวณเลขคณิตลอยตัว ( floatหรือdouble) โดยตรง

สำหรับตัวอย่างที่แปลกใหม่สถาปัตยกรรมคอมพิวเตอร์บางตัวรองรับเฉพาะพอยน์เตอร์ "เชิงคำ" (ชี้ไปที่จำนวนเต็มสองไบต์หรือสี่ไบต์ในหน่วยความจำ) และต้องใช้ตัวชี้ไบต์ ( char *หรือvoid *) โดยการเพิ่มฟิลด์ออฟเซ็ตพิเศษ คำถามนี้จะกล่าวถึงรายละเอียดบางอย่างเกี่ยวกับระบบดังกล่าว

ฟังก์ชัน "ไลบรารีรันไทม์" ที่อ้างถึงไม่ใช่ฟังก์ชันที่คุณจะเห็นในคู่มือ แต่เป็นฟังก์ชันเช่นนี้ในไลบรารีรันไทม์ของคอมไพเลอร์สมัยใหม่ซึ่งใช้ในการดำเนินการประเภทพื้นฐานที่เครื่องไม่รองรับ . ไลบรารีรันไทม์ที่ K&R อ้างถึงสามารถพบได้ในเว็บไซต์ของ The Unix Heritage Society - คุณสามารถดูฟังก์ชันต่างๆเช่นldiv(แตกต่างจากฟังก์ชัน C ที่มีชื่อเดียวกันซึ่งไม่มีอยู่ในขณะนั้น) ซึ่งใช้เพื่อใช้การแบ่งส่วน ค่า 32 บิตซึ่ง PDP-11 ไม่รองรับแม้จะมี Add-on และcsv(และcretใน csv.c) ซึ่งบันทึกและกู้คืนรีจิสเตอร์บนสแต็กเพื่อจัดการการโทรและการส่งคืนจากฟังก์ชัน

พวกเขามีแนวโน้มที่จะอ้างถึงตัวเลือกของพวกเขาที่จะไม่สนับสนุนประเภทข้อมูลจำนวนมากที่ไม่ได้รับการสนับสนุนโดยตรงจากเครื่องต้นแบบซึ่งแตกต่างจากภาษาร่วมสมัยอื่น ๆ เช่น FORTRAN ซึ่งมีความหมายของอาร์เรย์ที่ไม่ได้จับคู่กับตัวชี้พื้นฐานของ CPU เช่นกัน อาร์เรย์ของ C ความจริงที่ว่าอาร์เรย์ C มักจะมีการจัดทำดัชนีเป็นศูนย์และมีขนาดเท่าที่ทราบเสมอในทุกระดับ แต่วิธีแรกไม่จำเป็นต้องจัดเก็บช่วงดัชนีหรือขนาดของอาร์เรย์และไม่จำเป็นต้องมีฟังก์ชันไลบรารีรันไทม์เพื่อเข้าถึง - คอมไพเลอร์สามารถฮาร์ดโค้ดเลขคณิตตัวชี้ที่จำเป็น


3

คำสั่งนั้นหมายความว่าข้อมูลและโครงสร้างการควบคุมใน C เป็นข้อมูลเชิงเครื่องจักร

มีสองด้านที่ต้องพิจารณาที่นี่ ประการหนึ่งคือภาษา C มีคำจำกัดความ (มาตรฐาน ISO) ซึ่งอนุญาตให้ละติจูดในการกำหนดชนิดข้อมูลได้ ซึ่งหมายความว่าการใช้ภาษา C เหมาะกับเครื่อง ชนิดข้อมูลของคอมไพเลอร์ C ตรงกับสิ่งที่มีอยู่ในเครื่องซึ่งคอมไพลเลอร์กำหนดเป้าหมายเนื่องจากภาษามีละติจูดสำหรับสิ่งนั้น หากเครื่องมีขนาดคำผิดปกติเช่น 36 บิตให้พิมพ์intหรือlongกำหนดให้เป็นไปตามนั้นได้ โปรแกรมที่คิดว่าintเป็น 32 บิตจะพัง

ประการที่สองเนื่องจากปัญหาการพกพาดังกล่าวจึงมีผลประการที่สอง ในทางหนึ่งข้อความใน K&R ได้กลายเป็นคำทำนายที่ตอบสนองตนเองหรืออาจจะตรงกันข้าม กล่าวคือผู้ใช้โปรเซสเซอร์รุ่นใหม่ตระหนักถึงความจำเป็นอย่างยิ่งในการสนับสนุนคอมไพเลอร์ C และพวกเขารู้ว่ามีโค้ด C จำนวนมากซึ่งถือว่า "โปรเซสเซอร์ทุกตัวดูเหมือน 80386" สถาปัตยกรรมได้รับการออกแบบโดยคำนึงถึง C: และไม่เพียง แต่คำนึงถึง C เท่านั้น แต่ยังมีความเข้าใจผิดทั่วไปเกี่ยวกับการพกพา C อยู่ในใจด้วย คุณไม่สามารถแนะนำเครื่องที่มีขนาด 9 บิตหรืออะไรก็ตามเพื่อการใช้งานทั่วไปได้อีกต่อไป โปรแกรมที่สมมติว่าเป็นประเภทcharความกว้าง 8 บิตจะแตก มีเพียงบางโปรแกรมที่เขียนโดยผู้เชี่ยวชาญด้านการพกพาเท่านั้นที่จะยังคงใช้งานได้: อาจไม่เพียงพอที่จะดึงระบบที่สมบูรณ์เข้าด้วยกันด้วย toolchain เคอร์เนลพื้นที่ผู้ใช้และแอปพลิเคชันที่มีประโยชน์ด้วยความพยายามที่สมเหตุสมผล กล่าวอีกนัยหนึ่งประเภท C ดูเหมือนสิ่งที่มีอยู่ในฮาร์ดแวร์เนื่องจากฮาร์ดแวร์ถูกสร้างขึ้นให้ดูเหมือนฮาร์ดแวร์อื่น ๆ ที่เขียนโปรแกรม C ที่ไม่สามารถพกพาได้จำนวนมาก

มีตัวอย่างประเภทข้อมูลหรือโครงสร้างการควบคุมที่คอมพิวเตอร์ไม่รองรับโดยตรงหรือไม่

ชนิดข้อมูลไม่ได้รับการสนับสนุนโดยตรงในภาษาเครื่องจำนวนมาก: จำนวนเต็มหลายความแม่นยำ; รายการที่เชื่อมโยง; ตารางแฮช; สตริงอักขระ

โครงสร้างการควบคุมไม่ได้รับการสนับสนุนโดยตรงในภาษาเครื่องส่วนใหญ่: ความต่อเนื่องชั้นหนึ่ง coroutine / ด้าย; กำเนิด; การจัดการข้อยกเว้น

สิ่งเหล่านี้ต้องการรหัสสนับสนุนรันไทม์จำนวนมากที่สร้างขึ้นโดยใช้คำแนะนำสำหรับวัตถุประสงค์ทั่วไปจำนวนมากและประเภทข้อมูลพื้นฐานเพิ่มเติม

C มีข้อมูลมาตรฐานบางประเภทที่บางเครื่องไม่รองรับ ตั้งแต่ C99, C มีจำนวนเชิงซ้อน ซึ่งสร้างขึ้นจากค่าทศนิยมสองค่าและสร้างขึ้นเพื่อทำงานร่วมกับรูทีนไลบรารี บางเครื่องไม่มีหน่วยทศนิยมเลย

ข้อมูลบางประเภทยังไม่ชัดเจน หากเครื่องรองรับการกำหนดแอดเดรสหน่วยความจำโดยใช้รีจิสเตอร์หนึ่งตัวเป็นที่อยู่ฐานและอีกอันเป็นดิสเพลสเมนต์ที่ปรับขนาดหมายความว่าอาร์เรย์เป็นประเภทข้อมูลที่รองรับโดยตรงหรือไม่

นอกจากนี้เมื่อพูดถึงจุดลอยตัวยังมีมาตรฐาน: IEEE 754 floating-point เหตุใดคอมไพลเลอร์ C ของคุณจึงมีdoubleรูปแบบจุดลอยตัวที่โปรเซสเซอร์สนับสนุนไม่ใช่เพียงเพราะทั้งสองตกลงกันเท่านั้น แต่เป็นเพราะมีมาตรฐานอิสระสำหรับการแสดงนั้น


2

สิ่งต่างๆเช่น

  • รายการที่ใช้ในภาษาที่ใช้งานได้เกือบทั้งหมด

  • ข้อยกเว้น

  • Associative arrays (Maps) - รวมอยู่ในเช่น PHP และ Perl

  • เก็บขยะ .

  • ชนิดข้อมูล / โครงสร้างการควบคุมรวมอยู่ในหลายภาษา แต่ CPU ไม่รองรับโดยตรง


2

ควรเข้าใจว่าได้รับการสนับสนุนโดยตรงเนื่องจากการแม็ปกับชุดคำสั่งของโปรเซสเซอร์อย่างมีประสิทธิภาพ

  • การสนับสนุนโดยตรงสำหรับประเภทจำนวนเต็มคือกฎยกเว้น long (อาจต้องใช้รูทีนเลขคณิตเพิ่มเติม) และขนาดสั้น (อาจต้องมีการกำบัง)

  • การสนับสนุนโดยตรงสำหรับประเภทจุดลอยตัวจำเป็นต้องมี FPU เพื่อให้พร้อมใช้งาน

  • การสนับสนุนโดยตรงสำหรับฟิลด์บิตนั้นยอดเยี่ยม

  • โครงสร้างและอาร์เรย์ต้องการการคำนวณที่อยู่ซึ่งได้รับการสนับสนุนโดยตรงในระดับหนึ่ง

  • พอยน์เตอร์ได้รับการสนับสนุนโดยตรงผ่านการกำหนดแอดเดรสทางอ้อมเสมอ

  • goto / if / while / for / do ได้รับการสนับสนุนโดยตรงจากสาขาที่ไม่มีเงื่อนไข / เงื่อนไข

  • สามารถรองรับสวิตช์ได้โดยตรงเมื่อใช้ตารางกระโดด

  • การเรียกใช้ฟังก์ชันได้รับการสนับสนุนโดยตรงโดยใช้คุณสมบัติสแตก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.