Viết một Trình biên dịch bằng C. C. Tại sao lại viết trình dịch mã máy cho ngôn ngữ cấp thấp bằng ngôn ngữ cấp cao hơn?


13

Người hướng dẫn lớp Vi xử lý của tôi đã giao cho chúng tôi và nói:

"Viết một Trình biên dịch trong C." - Giáo sư yêu quý của tôi

Vì vậy, nó có vẻ hơi phi logic với tôi.

Nếu tôi không sai Ngôn ngữ hội là bước đầu tiên từ Mã máy đến hành trình của các ngôn ngữ cấp cao hơn. Ý tôi là C là ngôn ngữ cấp cao hơn hội. Vì vậy, điểm của việc viết một Trình biên dịch trong C là gì? Họ đã làm gì trong quá khứ trong khi không có ngôn ngữ C? Có phải họ đang viết Trình biên dịch mã máy?

Nó không có nghĩa đối với tôi khi viết một trình dịch mã máy cho một ngôn ngữ cấp thấp bằng ngôn ngữ cấp cao hơn.

Giả sử chúng ta đã tạo ra một kiến ​​trúc vi xử lý hoàn toàn mới mà thậm chí không có trình biên dịch C cho kiến ​​trúc đó. Trình biên dịch của chúng ta viết bằng C có thể mô phỏng kiến ​​trúc mới không? Ý tôi là nó sẽ vô dụng hay không?

Nhân tiện, tôi biết rằng GNU Assembler và Netwide Assembler đã được viết bằng C. Tôi cũng tự hỏi tại sao chúng được viết bằng C?

Cuối cùng, đây là mã nguồn ví dụ cho một trình biên dịch đơn giản mà Giáo sư của chúng tôi đã cung cấp cho chúng tôi:

// to compile, gcc assembler.c -o assembler
// No error check is provided.
// Variable names cannot start with 0-9.
// hexadecimals are twos complement.
// first address of the code section is zero, data section follows the code section.
//fout tables are formed: jump table, ldi table, label table and variable table.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//Converts a hexadecimal string to integer.
int hex2int( char* hex)  
{
    int result=0;

    while ((*hex)!='\0')
    {
        if (('0'<=(*hex))&&((*hex)<='9'))
            result = result*16 + (*hex) -'0';
        else if (('a'<=(*hex))&&((*hex)<='f'))
            result = result*16 + (*hex) -'a'+10;
        else if (('A'<=(*hex))&&((*hex)<='F'))
            result = result*16 + (*hex) -'A'+10; 
        hex++;
    }
    return(result);
}


main()
{   
    FILE *fp;
        char line[100];
        char *token = NULL;
    char *op1, *op2, *op3, *label;
    char ch;
    int  chch;

    int program[1000];
    int counter=0;  //holds the address of the machine code instruction




// A label is a symbol which mark a location in a program. In the example 
// program above, the string "lpp", "loop" and "lp1" are labels.
    struct label  
    {
        int location;
        char *label;
    };
    struct label labeltable[50]; //there can be 50 labels at most in our programs
    int nooflabels = 0; //number of labels encountered during assembly.




// Jump instructions cannot be assembled readily because we may not know the value of 
// the label when we encountered a jump instruction. This happens if the label used by
// that jump instruction appear below that jump instruction. This is the situation 
// with the label "loop" in the example program above. Hence, the location of jump 
// instructions must be stored.
    struct jumpinstruction   
    {
        int location;
        char *label;
    };
    struct jumpinstruction jumptable[100]; //There can be at most 100 jumps
    int noofjumps=0;  //number of jumps encountered during assembly.    




// The list of variables in .data section and their locations.
    struct variable
    {
        int location;
        char *name;
    };
    struct variable variabletable[50]; //There can be 50 varables at most.
    int noofvariables = 0;




//Variables and labels are used by ldi instructions.
//The memory for the variables are traditionally allocated at the end of the code section.
//Hence their addresses are not known when we assemble a ldi instruction. Also, the value of 
//a label may not be known when we encounter a ldi instruction which uses that label.
//Hence, the location of the ldi instructions must be kept, and these instructions must be 
//modified when we discover the address of the label or variable that it uses.
    struct ldiinstruction   
    {
        int location;
        char *name;
    };
    struct ldiinstruction lditable[100];
    int noofldis=0;




    fp = fopen("name_of_program","r");

    if (fp != NULL)
    {
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .code section
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )
                break;
        } 
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");  //get the instruction mnemonic or label

//========================================   FIRST PASS  ======================================================
            while (token)
            {
                if (strcmp(token,"ldi")==0)        //---------------LDI INSTRUCTION--------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");                                //get the 1st operand of ldi, which is the register that ldi loads
                    op2 = strtok(NULL,"\n\t\r ");                                //get the 2nd operand of ldi, which is the data that is to be loaded
                    program[counter]=0x1000+hex2int(op1);                        //generate the first 16-bit of the ldi instruction
                    counter++;                                                   //move to the second 16-bit of the ldi instruction
                    if ((op2[0]=='0')&&(op2[1]=='x'))                            //if the 2nd operand is twos complement hexadecimal
                        program[counter]=hex2int(op2+2)&0xffff;              //convert it to integer and form the second 16-bit 
                    else if ((  (op2[0])=='-') || ((op2[0]>='0')&&(op2[0]<='9')))       //if the 2nd operand is decimal 
                        program[counter]=atoi(op2)&0xffff;                         //convert it to integer and form the second 16-bit 
                    else                                                           //if the second operand is not decimal or hexadecimal, it is a laber or a variable.
                    {                                                               //in this case, the 2nd 16-bits of the ldi instruction cannot be generated.
                        lditable[noofldis].location = counter;                 //record the location of this 2nd 16-bit  
                        op1=(char*)malloc(sizeof(op2));                         //and the name of the label/variable that it must contain
                        strcpy(op1,op2);                                        //in the lditable array.
                        lditable[noofldis].name = op1;
                        noofldis++;                                             
                    }       
                    counter++;                                                     //skip to the next memory location 
                }                                       

                else if (strcmp(token,"ld")==0)      //------------LD INSTRUCTION---------------------         
                {
                    op1 = strtok(NULL,"\n\t\r ");                //get the 1st operand of ld, which is the destination register
                    op2 = strtok(NULL,"\n\t\r ");                //get the 2nd operand of ld, which is the source register
                    ch = (op1[0]-48)| ((op2[0]-48) << 3);        //form bits 11-0 of machine code. 48 is ASCII value of '0'
                    program[counter]=0x2000+((ch)&0x00ff);       //form the instruction and write it to memory
                    counter++;                                   //skip to the next empty location in memory
                }
                else if (strcmp(token,"st")==0) //-------------ST INSTRUCTION--------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jz")==0) //------------- CONDITIONAL JUMP ------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jmp")==0)  //-------------- JUMP -----------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");           //read the label
                    jumptable[noofjumps].location = counter;    //write the jz instruction's location into the jumptable 
                    op2=(char*)malloc(sizeof(op1));         //allocate space for the label                  
                    strcpy(op2,op1);                //copy the label into the allocated space
                    jumptable[noofjumps].label=op2;         //point to the label from the jumptable
                    noofjumps++;                    //skip to the next empty location in jumptable
                    program[counter]=0x5000;            //write the incomplete instruction (just opcode) to memory
                    counter++;                  //skip to the next empty location in memory.
                }               
                else if (strcmp(token,"add")==0) //----------------- ADD -------------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");    
                    op2 = strtok(NULL,"\n\t\r ");
                    op3 = strtok(NULL,"\n\t\r ");
                    chch = (op1[0]-48)| ((op2[0]-48)<<3)|((op3[0]-48)<<6);  
                    program[counter]=0x7000+((chch)&0x00ff); 
                    counter++; 
                }
                else if (strcmp(token,"sub")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"and")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"or")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"xor")==0)
                {
                    //to be added
                }                       
                else if (strcmp(token,"not")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    op2 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op2[0]-48)<<3);
                    program[counter]=0x7500+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"mov")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"inc")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op1[0]-48)<<3);
                    program[counter]=0x7700+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"dec")==0)
                {
                                    //to be added
                }
                else //------WHAT IS ENCOUNTERED IS NOT AN INSTRUCTION BUT A LABEL. UPDATE THE LABEL TABLE--------
                {
                    labeltable[nooflabels].location = counter;  //buraya bir counter koy. error check
                    op1=(char*)malloc(sizeof(token));
                    strcpy(op1,token);
                    labeltable[nooflabels].label=op1;
                    nooflabels++;
                } 
                token = strtok(NULL,",\n\t\r ");  
            }
        }


//================================= SECOND PASS ==============================

                //supply the address fields of the jump and jz instructions from the 
        int i,j;         
        for (i=0; i<noofjumps;i++)                                                                   //for all jump/jz instructions
        {
            j=0;
            while ( strcmp(jumptable[i].label , labeltable[j].label) != 0 )             //if the label for this jump/jz does not match with the 
                j++;                                                                // jth label in the labeltable, check the next label..
            program[jumptable[i].location] +=(labeltable[j].location-jumptable[i].location-1)&0x0fff;       //copy the jump address into memory.
        }                                                     




                // search for the start of the .data segment
        rewind(fp);  
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .data, if no .data, also ok.
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".data")==0 )
                break;

        }


                // process the .data segment and generate the variabletable[] array.
        int dataarea=0;
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )  //go till the .code segment
                break;
            else if (token[strlen(token)-1]==':')
            {               
                token[strlen(token)-1]='\0';  //will not cause memory leak, as we do not do malloc
                variabletable[noofvariables].location=counter+dataarea;
                op1=(char*)malloc(sizeof(token));
                strcpy(op1,token);
                variabletable[noofvariables].name=op1;
                token = strtok(NULL,",\n\t\r ");
                if (token==NULL)
                    program[counter+dataarea]=0;
                else if (strcmp(token, ".space")==0)
                {
                    token=strtok(NULL,"\n\t\r ");
                    dataarea+=atoi(token);
                }
                else if((token[0]=='0')&&(token[1]=='x')) 
                    program[counter+dataarea]=hex2int(token+2)&0xffff; 
                else if ((  (token[0])=='-') || ('0'<=(token[0])&&(token[0]<='9'))  )
                    program[counter+dataarea]=atoi(token)&0xffff;  
                noofvariables++;
                dataarea++;
            }
        }






// supply the address fields for the ldi instructions from the variable table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<noofvariables)&&( strcmp( lditable[i].name , variabletable[j].name)!=0 ))
                j++;
            if (j<noofvariables)
                program[lditable[i].location] = variabletable[j].location;              
        } 

// supply the address fields for the ldi instructions from the label table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<nooflabels)&&( strcmp( lditable[i].name , labeltable[j].label)!=0 ))
                j++;
            if (j<nooflabels){
                program[lditable[i].location] = (labeltable[j].location)&0x0fff;
                printf("%d %d %d\n", i, j, (labeltable[j].location));   
            }           
        } 

//display the resulting tables
        printf("LABEL TABLE\n");
        for (i=0;i<nooflabels;i++)
            printf("%d %s\n", labeltable[i].location, labeltable[i].label); 
        printf("\n");
        printf("JUMP TABLE\n");
        for (i=0;i<noofjumps;i++)
            printf("%d %s\n", jumptable[i].location, jumptable[i].label);   
        printf("\n");
        printf("VARIABLE TABLE\n");
        for (i=0;i<noofvariables;i++)
            printf("%d %s\n", variabletable[i].location, variabletable[i].name);    
        printf("\n");
        printf("LDI INSTRUCTIONS\n");
        for (i=0;i<noofldis;i++)
            printf("%d %s\n", lditable[i].location, lditable[i].name);  
        printf("\n");
        fclose(fp);
        fp = fopen("RAM","w");
        fprintf(fp,"v2.0 raw\n");
        for (i=0;i<counter+dataarea;i++)
            fprintf(fp,"%04x\n",program[i]);
    }   
}

2
Không có thiết bị tồn tại trong sự cô lập. Các công cụ chéo rất phổ biến, đặc biệt đối với các kiến ​​trúc nhỏ.
Lars Viklund

3
Trình biên dịch / trình biên dịch "chéo" chạy trên một hệ thống khác với mục tiêu và tạo ra các tạo phẩm phù hợp để sử dụng trên hệ thống đích. Vào thời cổ đại, bạn không nhất thiết phải trao đổi dữ liệu giữa các hệ thống, nhưng phải tự khởi động lại một hệ thống từ đầu. Khá nhiều sự phát triển hiện đại cho các kiến ​​trúc được thực hiện trên các hệ thống đã được thiết lập, biên dịch chéo mọi thứ.
Lars Viklund

19
Bạn có muốn viết trình biên dịch mã trong mã máy thay vì C không? Giáo sư của bạn đang tốt với bạn
Winston Ewert

2
Tại sao bạn không cố gắng viết tất cả mã của mình trong môi trường / ngôn ngữ lập trình tốt nhất có thể? Một người lắp ráp cũng không ngoại lệ.
Erik Eidt

1
Không có một "hành trình" cố định theo bất kỳ hướng cụ thể nào.
tên gì là

Câu trả lời:


17

Mọi người đã viết lắp ráp trong mã máy. Sau đó, họ cũng đã viết bằng ngôn ngữ hợp ngữ - thường là một tập hợp con của ngôn ngữ mà họ tự dịch, vì vậy họ bắt đầu với một phiên bản "bootstrap" đơn giản của trình biên dịch, sau đó thêm các tính năng vào đó khi họ cần chúng cho chính trình biên dịch.

Tuy nhiên, không ai trong số này là đặc biệt cần thiết. Cuối cùng, một trình biên dịch là một chương trình dịch đơn giản (thường khá). Nó nhận một tệp ở định dạng một (văn bản) và viết ra một tệp ở dạng khác (thường là định dạng tệp đối tượng).

Thực tế là văn bản đầu vào đại diện cho hướng dẫn máy ở định dạng văn bản và kết quả thể hiện cùng một hướng dẫn ở định dạng nhị phân không tạo ra nhiều khác biệt so với ngôn ngữ được sử dụng để triển khai trình biên dịch - thực tế, thậm chí cao hơn ngôn ngữ C vì SNOBOL và Python có thể hoạt động khá độc đáo - gần đây tôi đã làm việc với một trình biên dịch được viết bằng Python và nó hoạt động khá tốt cho công việc.

Theo như cách bạn khởi động mọi thứ ban đầu: thông thường trên một máy khác có các công cụ phát triển khá và tương tự. Nếu bạn đang phát triển phần cứng mới, bạn thường bắt đầu bằng cách viết trình giả lập (hoặc ít nhất là trình giả lập) cho máy mới, vì vậy, lúc đầu, bạn đang xây dựng và chạy mã trên một số hệ thống máy chủ trong mọi trường hợp.


3
"Các ngôn ngữ thậm chí cao hơn C như SNOBOL và Python có thể hoạt động khá độc đáo" - đây là một điểm rất tốt. Đối với NASM, chúng tôi chưa bao giờ thực sự coi bất cứ thứ gì cấp cao hơn C, nhưng đó là năm 1995 khi hiệu suất quan trọng hơn nhiều so với ngày nay và các ngôn ngữ cấp cao kém hơn rất nhiều so với ngày nay. Ngày nay, nó chắc chắn đáng để xem xét các lựa chọn thay thế.
Jules

1
Tôi đã không nghe cái tên SNOBOL từ những năm 1980.
pacmaninbw

Tôi đã viết một trình biên dịch trong Haskell một lần. Việc đánh giá lười biếng và xâu chuỗi chức năng khiến việc viết một trình tối ưu hóa lổ nhìn trộm cho mã máy được tạo ra thật đơn giản.
Thorbjørn Ravn Andersen

10

Bạn đang thấy các kết nối không tồn tại.

"Viết một trình biên dịch" là một nhiệm vụ lập trình giống như bất kỳ nhiệm vụ lập trình nào khác. Bạn sử dụng các công cụ để xử lý nhiệm vụ đó là tốt nhất cho nhiệm vụ đó. Không có gì đặc biệt về việc viết một trình biên dịch; không có lý do nào để không viết nó bằng một ngôn ngữ cấp cao. C thực sự ở mức khá thấp, và tôi có lẽ sẽ thích C ++ hoặc một số ngôn ngữ cấp cao khác.

Ngôn ngữ hội thực sự hoàn toàn không phù hợp cho một nhiệm vụ như thế này. Các trường hợp bạn sử dụng hợp lý ngôn ngữ lắp ráp là rất, rất hiếm. Chỉ khi bạn cần làm những việc không thể diễn đạt bằng ngôn ngữ cấp cao hơn.


1
Các câu trả lời khác là rất tốt, nhưng tôi thấy câu này là đơn giản nhất, đặc biệt là với hai câu đầu tiên. Tôi đã nói với bản thân mình điều tương tự khi đọc câu hỏi.
MetalMikester

Viết bằng tay ngôn ngữ lắp ráp ngày nay chỉ cần cho các bản hack phần cứng cụ thể. Ví dụ, thiết lập chế độ được bảo vệ trên một số CPU yêu cầu trình tự lệnh cụ thể và trình tự tương đương logic là không đủ. Khá nhiều tất cả các chương trình bình thường không yêu cầu bất kỳ chuỗi hướng dẫn cụ thể nào cho nhiệm vụ họ cần thực hiện và kết quả là không có lý do nào để yêu cầu bất kỳ chuỗi cụ thể nào mà chỉ có một số bộ hướng dẫn tương đương logic. Tối ưu hóa trình biên dịch thực hiện chính xác điều tương tự để cải thiện hiệu suất thực thi (số lệnh, thời gian đồng hồ treo tường, kích thước bộ đệm mã).
Mikko Rantalainen

9

Họ đã làm gì trong quá khứ trong khi không có ngôn ngữ C? Có phải họ đang viết Trình biên dịch mã máy?

Hội về cơ bản là một bản ghi nhớ cho mã máy; mỗi opcode trong ngôn ngữ máy được cung cấp một mnemonic lắp ráp tức là trong x86 NOP là 0x90. Điều này làm cho trình biên dịch khá đơn giản (hầu hết các trình biên dịch có hai lần chuyển, một lần dịch và lần thứ hai để tạo / giải quyết địa chỉ / tham chiếu.) Trình biên dịch đầu tiên được viết và dịch bằng tay (có thể trên giấy) thành mã máy. Một phiên bản tốt hơn được viết và lắp ráp với trình biên dịch 'lắp ráp' bằng tay, các tính năng mới được thêm vào theo cách này. Trình biên dịch cho các ngôn ngữ mới có thể được xây dựng theo cách này; trong quá khứ, thông thường các trình biên dịch để lắp ráp đầu ra và sử dụng một trình biên dịch cho phần cuối của chúng!

Nó không có nghĩa đối với tôi khi viết một trình dịch mã máy cho một ngôn ngữ cấp thấp bằng ngôn ngữ cấp cao hơn. ... [Trình biên dịch hiện có] đã được viết bằng C. Tôi cũng tự hỏi tại sao chúng được viết bằng C?

  • Nói chung là dễ dàng hơn để viết một phần mềm phức tạp hơn bằng ngôn ngữ cấp cao hơn.
  • Nó thường mất nhiều mã hơn và nhiều nỗ lực tinh thần hơn để theo dõi những gì bạn đang làm bằng ngôn ngữ cấp thấp hơn ngôn ngữ cao hơn.
    • Một dòng C có thể dịch thành nhiều hướng dẫn cũ. một phép gán đơn giản trong C ++ (hoặc C) thường tạo ra ít nhất 3 lệnh lắp ráp (tải, sửa đổi, lưu trữ;) có thể mất hai mươi lệnh trở lên (có thể hàng trăm,) để thực hiện những gì có thể được thực hiện với một dòng ở cấp cao hơn ngôn ngữ (như c ++ hoặc c.) Người ta thường muốn dành thời gian của họ để giải quyết vấn đề và không dành thời gian để tìm ra cách thực hiện giải pháp trong mã máy.

Mặc dù tự lưu trữ là một tính năng quan trọng / mong muốn phổ biến đối với ngôn ngữ lập trình, lắp ráp ở mức độ thấp đến mức hầu hết các lập trình viên muốn làm việc ở cấp độ cao hơn. Tức là không ai muốn viết một trình biên dịch hợp ngữ (hoặc bất cứ thứ gì khác thực sự)

Giả sử chúng ta đã tạo ra một kiến ​​trúc vi xử lý hoàn toàn mới mà thậm chí không có trình biên dịch C cho kiến ​​trúc đó.

Bootstrapping là quá trình nhận chuỗi công cụ trên một kiến ​​trúc mới.

quy trình cơ bản là:

  • viết một back end mới hiểu cách tạo mã cho CPU mới (hoặc MCU) của bạn
  • biên dịch và kiểm tra mặt sau của bạn
  • biên dịch chéo trình biên dịch mong muốn của bạn (và os, v.v.) bằng cách sử dụng back-end mới của bạn
  • chuyển các tệp nhị phân này sang hệ thống mới

Không phải một lần bạn cần phải viết trong assembly (mới hay cũ) để làm điều này, người ta nên chọn ngôn ngữ tốt nhất để viết trình biên dịch / back-end / code-Generator của bạn.

Trình biên dịch của chúng ta viết bằng C có thể mô phỏng kiến ​​trúc mới không?

Nhà lắp ráp không mô phỏng!

Nếu một người đang phát triển CPU mới với ngôn ngữ máy mới (hoặc hiện có), một trình giả lập thường là cần thiết để thử nghiệm; tức là chạy các hướng dẫn và dữ liệu ngẫu nhiên thông qua trình giả lập và so sánh đầu ra với cùng các hướng dẫn và dữ liệu trên CPU nguyên mẫu của bạn. Sau đó tìm lỗi, sửa lỗi, lặp lại.


3

Trong số các lý do để viết một trình biên dịch bằng C (hoặc bất kỳ ngôn ngữ cấp cao nào khác) là tất cả các lý do bạn có thể sử dụng để biện minh cho việc viết bất kỳ chương trình nào khác bằng ngôn ngữ cấp cao hơn đó. Trưởng trong số những người trong trường hợp này có lẽ là tính di động và khả năng sử dụng.

Tính di động: Nếu bạn viết trình biên dịch của mình bằng ngôn ngữ bản địa, bạn có một trình biên dịch trên nền tảng đó. Nếu bạn viết nó bằng C, bạn có trình biên dịch trên bất kỳ nền tảng nào có trình biên dịch C. Điều này cho phép bạn, ví dụ, biên dịch mã cho nền tảng nhúng của bạn trên máy trạm và di chuyển nhị phân thay vì cần phải thực hiện tất cả trực tiếp trên thiết bị đích.

Khả năng sử dụng: Đối với hầu hết mọi người, việc đọc, suy luận và sửa đổi chương trình sẽ tự nhiên hơn rất nhiều khi chương trình ở ngôn ngữ cấp cao hơn so với khi trình biên dịch mã hoặc (mã tệ hơn). Do đó, việc phát triển và duy trì trình biên dịch theo ngôn ngữ cấp cao hơn sẽ dễ dàng hơn vì bạn có thể nghĩ về các khái niệm trừu tượng dành cho bạn bởi các ngôn ngữ cấp cao hơn thay vì phải suy nghĩ về những chi tiết vụn vặt mà bạn chịu trách nhiệm ở những ngôn ngữ thấp hơn.


3

Chỉ giải quyết cụ thể phần này của câu hỏi:

"Nhân tiện, tôi biết rằng GNU Assembler và Netwide Assembler đã được viết bằng C. Tôi cũng đang tự hỏi tại sao chúng lại được viết bằng C?"

Phát biểu như một phần của nhóm ban đầu đã viết Trình biên dịch Netwide, quyết định dường như quá rõ ràng đối với chúng tôi vào thời điểm đó về cơ bản chúng tôi không xem xét bất kỳ lựa chọn nào khác, nhưng chúng tôi đã thực hiện nên chúng tôi sẽ đưa ra kết luận tương tự, dựa trên những lý do sau:

  • Viết nó bằng ngôn ngữ cấp thấp sẽ khó hơn và tốn nhiều thời gian hơn.
  • Viết nó bằng ngôn ngữ cấp cao hơn có thể nhanh hơn, nhưng đã có những cân nhắc về hiệu năng (một trình biên dịch được sử dụng làm phần cuối cho trình biên dịch, đặc biệt, cần phải rất nhanh để tránh làm chậm trình biên dịch xuống quá nhiều, vì nó có thể cuối cùng xử lý số lượng mã rất lớn và đây là trường hợp sử dụng mà chúng tôi đặc biệt muốn cho phép) và tôi không tin rằng các tác giả chính có bất kỳ ngôn ngữ cấp cao nào (đây là trước khi Java trở nên phổ biến, vì vậy thế giới những ngôn ngữ như vậy đã bị phân mảnh trở lại sau đó). Chúng tôi đã sử dụng perl cho một số tác vụ siêu lập trình (tạo các bảng hướng dẫn theo định dạng hữu ích cho phần cuối của trình tạo mã), nhưng nó thực sự không phù hợp với toàn bộ chương trình.
  • Chúng tôi muốn tính di động của hệ điều hành
  • Chúng tôi muốn tính di động của nền tảng phần cứng (để sản xuất trình biên dịch chéo)

Điều này đưa ra quyết định khá dễ dàng: C tuân thủ ANSI (hay còn gọi là C89 ngày nay) là ngôn ngữ duy nhất tại thời điểm thực sự đạt được tất cả những điểm đó. Nếu đã có một C ++ được tiêu chuẩn hóa thì chúng ta có thể đã cân nhắc điều đó, nhưng sự hỗ trợ của C ++ giữa các hệ thống khác nhau khá khó khăn, vì vậy việc viết C ++ di động là một cơn ác mộng.


1

Một điều hoàn toàn không có gì để làm với điều khác. Các trình duyệt web có được viết bằng html hoặc php hoặc một số ngôn ngữ nội dung web khác không? Không, tại sao họ? Những chiếc xe chỉ có thể được điều khiển bởi những chiếc xe khác chứ không phải bởi con người?

Chuyển đổi một blob bit (một số ascii) sang một blob bit khác (một số mã máy) chỉ là một nhiệm vụ lập trình, ngôn ngữ lập trình bạn sử dụng cho nhiệm vụ đó là bất cứ điều gì bạn muốn. Bạn có thể và đã có những người lắp ráp được viết bằng nhiều ngôn ngữ khác nhau.

Các ngôn ngữ mới không thể được viết bằng ngôn ngữ của chúng ban đầu vì chưa có trình biên dịch / trình biên dịch cho chúng. Nếu không có trình biên dịch hiện có cho một ngôn ngữ mới, bạn phải viết ngôn ngữ đầu tiên bằng một số ngôn ngữ khác và sau đó bạn cuối cùng sẽ khởi động nếu điều đó thậm chí có ý nghĩa với bootstrap. (html và trình duyệt web, một chương trình lấy một số bit và nhổ một số bit ra sẽ không bao giờ được viết bằng html, không thể).

Không phải là một ngôn ngữ mới, có thể là một ngôn ngữ hiện có. Trình biên dịch C hoặc C ++ mới không tự động biên dịch ngay ra khỏi cổng.

Đối với ngôn ngữ hợp ngữ và C, hai ngôn ngữ đầu tiên cho hầu hết các tập lệnh mới hoặc được sửa đổi. Chúng ta không ở trong quá khứ, chúng ta ở hiện tại. Chúng ta có thể dễ dàng tạo một trình biên dịch trong C hoặc java hoặc python hoặc bất cứ thứ gì cho bất kỳ tập lệnh và ngôn ngữ lắp ráp nào chúng ta muốn ngay cả khi nó chưa tồn tại. Tương tự như vậy, có nhiều trình biên dịch C có thể điều chỉnh lại mà chúng ta có thể xuất ngôn ngữ hợp ngữ cho bất kỳ ngôn ngữ lắp ráp nào chúng ta muốn ngay cả khi trình biên dịch chưa tồn tại.

Đó chính xác là những gì chúng tôi làm với một bộ hướng dẫn mới. Lấy một số máy tính không chạy trên tập lệnh mới của chúng tôi với trình biên dịch C được biên dịch không dành cho tập lệnh mới cũng như trình biên dịch chương trình, tạo trình biên dịch chéo và trình biên dịch chéo. Phát triển và sử dụng điều đó trong khi tạo và mô phỏng logic. Trải qua các chu kỳ phát triển bình thường để tìm lỗi và sửa lỗi và kiểm tra lại, cho đến khi lý tưởng tất cả các công cụ và logic được coi là đã sẵn sàng. Và tùy thuộc vào mục tiêu, giả sử đó là một vi điều khiển không có khả năng chạy hệ điều hành, bạn sẽ không bao giờ có lý do để bootstrap sao cho toolchain tạo và chạy bằng tập lệnh gốc. Bạn sẽ luôn luôn biên dịch chéo. Khác với máy quay ngược, không bao giờ có ý nghĩa khi viết trình biên dịch trong trình biên dịch chương trình.

Có, nếu bạn có thể quay lại hoặc giả vờ quay lại, trình biên dịch đầu tiên là một con người với một cây bút chì và giấy, nó đã viết một cái gì đó có ý nghĩa với họ và sau đó viết các bit bên cạnh nó có ý nghĩa với logic. Sau đó, sử dụng các công tắc hoặc một số cách khác để đưa các bit vào máy (google pdp8 hoặc pdp11 hoặc altair 8800) và làm cho nó làm một cái gì đó. Không có trình giả lập máy tính ban đầu, bạn chỉ cần có được logic bằng cách nhìn chằm chằm vào nó đủ lâu hoặc cũng có thể quay vài vòng quay của chip. Các công cụ đủ tốt ngày hôm nay để bạn có thể đạt được thành công A0 ở chỗ nó không chỉ là một điện trở lớn, rất nhiều công cụ bạn vẫn có thể cần một vòng quay cho những thứ bạn không thể mô phỏng hoàn toàn, nhưng bạn có thể thường xuyên khởi động ngay bây giờ spi đầu tiên mà không phải chờ đến vòng quay thứ ba hoặc thứ tư,

Trong máy quay ngược lại như bạn mong đợi, sau đó bạn lấy mã được lắp ráp bằng tay và bạn sử dụng nó để nói tải một chương trình từ băng hoặc thẻ. Bạn cũng tự tay mã hóa một trình biên dịch mã trong máy, có thể không phải là toàn bộ mà là một công cụ giúp việc lập trình dễ dàng hơn một chút. Sau đó, công cụ đó được sử dụng để tạo một ngôn ngữ có thể xử lý một ngôn ngữ nâng cao hoặc phức tạp hơn (trình biên dịch macro) và công cụ đó để tạo một phức tạp hơn và bạn kết thúc với FORTRAN hoặc BASIC hoặc B hoặc bất cứ điều gì. Và sau đó bạn bắt đầu nghĩ về bootstrapping trong cùng một ngôn ngữ, viết lại trình biên dịch chéo thành trình biên dịch gốc. tất nhiên bạn lý tưởng cần một môi trường hoặc hệ điều hành nào đó cho việc đó.

Khi chúng ta tạo hoặc kiểm tra silicon, chúng ta có thể / cần nhìn chằm chằm vào các tín hiệu là số không và số không. Các công cụ sẽ hiển thị cho chúng tôi nhị phân hoặc hex theo mặc định và một số công cụ thậm chí có thể có tra cứu để các công cụ sẽ hiển thị một số ghi nhớ (có lẽ là lắp ráp) nhưng thường các kỹ sư (silicon / phần cứng và phần mềm) có thể đọc đủ mã máy hoặc sử dụng phần tách / liệt kê để "xem" hướng dẫn.

Tùy thuộc vào những gì bạn đang làm, bạn có thể chuyển một số mã máy vào các vectơ kiểm tra thay vì viết lại và biên dịch lại hoặc lắp ráp lại kiểm tra. Ví dụ: nếu bạn có một đường ống dẫn và tìm nạp trước ở một số độ sâu bạn có thể cần hoặc muốn điền vào cuối chương trình, một số lệnh hoặc các hướng dẫn thực tế khác để đường ống không bị sai theo các hướng dẫn không xác định và bạn chỉ có thể chọn chỉ điền mã máy vào danh sách / tệp ở mức thấp nhất thay vì cố gắng để trình biên dịch hoặc trình biên dịch hoặc trình liên kết thực hiện.

Trong khi kiểm tra bộ xử lý tất nhiên bạn cần phải xử lý các bit không xác định và có lẽ không quan tâm đến bit, v.v. Vì vậy, bạn sẽ cần phải nhập mã máy và sửa đổi một hoặc nhiều bit cụ thể trong một lệnh trong chương trình hoạt động bình thường. Có đáng để viết một chương trình để làm điều này hay chỉ làm bằng tay. Tương tự như vậy khi kiểm tra ECC, bạn muốn lật một hoặc nhiều bit và thấy rằng chúng bị sửa hoặc bị mắc kẹt. Được cho là dễ dàng hơn nhiều để viết một chương trình để làm hoặc bạn chỉ có thể làm nó bằng tay.

Tất nhiên, có những ngôn ngữ không tạo mã chạy trên bộ xử lý, pascal đầu tiên, java, python, v.v. Bạn cần một VM được viết bằng một số ngôn ngữ khác chỉ để sử dụng các ngôn ngữ đó. Bạn không thể sử dụng trình biên dịch java của mình để tạo java vm, không có ý nghĩa gì dựa trên thiết kế của ngôn ngữ.

. cần. Một ví dụ về gnu java để gcc chẳng hạn).

Theo thời gian và có lẽ chúng tôi vẫn không viết trình biên dịch C bằng C. Chúng tôi sử dụng những thứ như bison / flex một số ngôn ngữ lập trình khác mà chúng tôi sử dụng để tạo C cho chúng tôi mà chúng tôi không muốn tự viết. Một số phần trăm là trong C chắc chắn, nhưng một số phần trăm trong một số ngôn ngữ khác sử dụng một số trình biên dịch khác nhập bit và xuất các bit khác. Đôi khi cách tiếp cận này cũng được sử dụng để tạo một trình biên dịch chương trình. Tùy thuộc vào người thiết kế trình biên dịch / trình biên dịch (các chương trình có nhiệm vụ nhập các bit và sau đó xuất các bit khác) về cách chúng sẽ thực hiện nó. Các trình phân tích cú pháp tạo chương trình có thể được lập trình bằng tay chắc chắn, chỉ tốn thời gian để mọi người tìm kiếm một phím tắt. Giống như bạn có thể viết một trình biên dịch trong trình biên dịch chương trình nhưng mọi người tìm kiếm một lối tắt.

Một trình duyệt web chỉ là một chương trình lấy một số bit và phun ra một số bit khác. Trình biên dịch mã chỉ là một chương trình lấy một số bit và phun ra một số bit khác. Một trình biên dịch chỉ là một chương trình lấy một số bit và phun ra một số bit khác. V.v. Đối với tất cả những điều này, có một bộ quy tắc được lập thành tài liệu cho các bit đầu vào và bit đầu ra cho mỗi tác vụ lập trình. Các tác vụ và bit này đủ chung để có thể sử dụng bất kỳ ngôn ngữ lập trình AVAILABLE nào (có khả năng thực hiện thao tác bit / byte và xử lý các đầu vào và đầu ra). Chìa khóa ở đây là có sẵn. Nhận và thử linux từ sách hướng dẫn / hướng dẫn. Hãy thử pdp8 hoặc pdp11 hoặc altair 8800 hoặc trình giả lập khác với bảng điều khiển phía trước mô phỏng.

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.