Tại sao * khai báo * dữ liệu và hàm cần thiết trong ngôn ngữ C, khi định nghĩa được viết ở cuối mã nguồn?


15

Hãy xem xét mã "C" sau:

#include<stdio.h>
main()
{   
  printf("func:%d",Func_i());   
}

Func_i()
{
  int i=3;
  return i;
}

Func_i()được định nghĩa ở cuối mã nguồn và không có khai báo nào được cung cấp trước khi sử dụng main(). Đồng thời rất khi trình biên dịch thấy Func_i()trong main(), nó đi kèm ra khỏi main()và phát hiện ra Func_i(). Trình biên dịch bằng cách nào đó tìm thấy giá trị được trả về Func_i()và đưa nó vào printf(). Tôi cũng biết rằng trình biên dịch không thể tìm thấy kiểu trả về Func_i(). Nó, theo mặc định mất (đoán?) Các kiểu trả về của Func_i()được int. Đó là nếu mã đã có float Func_i()thì trình biên dịch sẽ báo lỗi: Các kiểu xung đột choFunc_i() .

Từ các cuộc thảo luận ở trên, chúng ta thấy rằng:

  1. Trình biên dịch có thể tìm thấy giá trị được trả về bởi Func_i().

    • Nếu trình biên dịch có thể tìm thấy giá trị được trả về bằng Func_i()cách đi ra main()và tìm kiếm mã nguồn, thì tại sao nó không thể tìm thấy loại Func_i (), được đề cập rõ ràng .
  2. Trình biên dịch phải biết rằng đó Func_i()là kiểu float - đó là lý do tại sao nó đưa ra lỗi của các kiểu xung đột.

  • Nếu trình biên dịch biết rằng đó Func_ilà kiểu float, thì tại sao nó vẫn giả sử Func_i()là kiểu int và đưa ra lỗi của các kiểu xung đột? Tại sao nó không thực Func_i()sự là loại nổi.

Tôi có cùng nghi ngờ với khai báo biến . Hãy xem xét mã "C" sau:

#include<stdio.h>
main()
{
  /* [extern int Data_i;]--omitted the declaration */
  printf("func:%d and Var:%d",Func_i(),Data_i);
}

 Func_i()
{
  int i=3;
  return i;
}
int Data_i=4;

Trình biên dịch đưa ra lỗi: 'Data_i' không được khai báo (lần đầu tiên sử dụng chức năng này).

  • Khi trình biên dịch nhìn thấy Func_i(), nó đi xuống mã nguồn để tìm giá trị được trả về bởi Func_ (). Tại sao trình biên dịch không thể làm tương tự cho biến Data_i?

Biên tập:

Tôi không biết chi tiết về hoạt động bên trong của trình biên dịch, trình biên dịch, bộ xử lý, v.v. Ý tưởng cơ bản của câu hỏi của tôi là nếu tôi nói (ghi) giá trị trả về của hàm trong mã nguồn cuối cùng, sau khi sử dụng của chức năng đó thì ngôn ngữ "C" cho phép máy tính tìm giá trị đó mà không đưa ra bất kỳ lỗi nào. Bây giờ tại sao máy tính không thể tìm thấy loại tương tự. Tại sao không thể tìm thấy loại Data_i làm giá trị trả về của Func_i (). Ngay cả khi tôi sử dụng extern data-type identifier;câu lệnh, tôi không nói giá trị được trả về bởi mã định danh (hàm / biến) đó. Nếu máy tính có thể tìm thấy giá trị đó thì tại sao nó không thể tìm thấy loại. Tại sao chúng ta cần tuyên bố chuyển tiếp ở tất cả?

Cảm ơn bạn.


7
Trình biên dịch không "tìm" giá trị được trả về bởi Func_i. điều này được thực hiện tại thời điểm thực hiện.
James McLeod

26
Tôi đã không downvote, nhưng câu hỏi dựa trên một số hiểu lầm nghiêm trọng về cách trình biên dịch hoạt động và phản hồi của bạn trong các bình luận cho thấy bạn vẫn còn một số trở ngại về mặt khái niệm cần khắc phục.
James McLeod

4
Lưu ý rằng mã mẫu đầu tiên không hợp lệ, mã tuân thủ tiêu chuẩn trong mười lăm năm qua; C99 không có kiểu trả về trong định nghĩa hàm và khai báo ngầmFunc_i không hợp lệ. Không bao giờ có một quy tắc để khai báo ngầm định các biến không xác định, vì vậy đoạn thứ hai luôn bị sai. (Có, trình biên dịch vẫn chấp nhận mẫu đầu tiên vì nó hợp lệ, nếu cẩu thả, theo C89 / C90.)
Jonathan Leffler

19
@ user31782: Dòng dưới cùng cho câu hỏi: Tại sao ngôn ngữ X làm / yêu cầu Y? Bởi vì đó là sự lựa chọn của các nhà thiết kế. Bạn dường như đang tranh luận rằng các nhà thiết kế của một trong những ngôn ngữ thành công nhất từng có những lựa chọn khác nhau từ nhiều thập kỷ trước thay vì cố gắng hiểu những lựa chọn đó trong bối cảnh chúng được tạo ra. Câu trả lời cho câu hỏi của bạn: Tại sao chúng ta cần khai báo chuyển tiếp? đã được đưa ra: Bởi vì C sử dụng trình biên dịch một lượt. Câu trả lời đơn giản nhất cho hầu hết các câu hỏi tiếp theo của bạn là bởi vì sau đó nó sẽ không phải là trình biên dịch một lượt.
Mr.Mindor 23/12/14

4
@ user31782 Bạn thực sự, thực sự muốn đọc cuốn sách của rồng để hiểu về cách trình biên dịch và bộ xử lý thực sự hoạt động - không thể chắt lọc tất cả kiến ​​thức cần thiết vào một câu trả lời SO (hoặc thậm chí 100 ở đó). Cuốn sách tuyệt vời cho bất cứ ai có hứng thú với trình biên dịch.
Voo

Câu trả lời:


26

Vì C là đường chuyền đơn , tĩnh đánh máy , một cách yếu ớt, đánh máy , biên soạn ngôn ngữ.

  1. Single-pass có nghĩa là trình biên dịch không nhìn về phía trước để xem định nghĩa của hàm hoặc biến. Vì trình biên dịch không nhìn về phía trước, nên việc khai báo hàm phải xuất hiện trước khi sử dụng hàm, nếu không trình biên dịch không biết chữ ký loại của nó là gì. Tuy nhiên, định nghĩa của hàm có thể sau này trong cùng một tệp hoặc thậm chí trong một tệp khác hoàn toàn. Xem điểm # 4.

    Ngoại lệ duy nhất là tạo tác lịch sử rằng các hàm và biến không được khai báo được cho là thuộc loại "int". Thực hành hiện đại là để tránh gõ ngầm bằng cách luôn luôn khai báo các hàm và biến rõ ràng.

  2. Nhập tĩnh có nghĩa là tất cả các thông tin loại được tính toán tại thời gian biên dịch. Thông tin đó sau đó được sử dụng để tạo mã máy thực thi trong thời gian chạy. Không có khái niệm trong C về thời gian gõ. Một lần int, luôn luôn là int, một lần float, luôn là float. Tuy nhiên, thực tế đó có phần bị che khuất bởi điểm tiếp theo.

  3. Gõ yếu có nghĩa là trình biên dịch C tự động tạo mã để chuyển đổi giữa các loại số mà không yêu cầu lập trình viên chỉ định rõ ràng các hoạt động chuyển đổi. Do gõ tĩnh, việc chuyển đổi tương tự sẽ luôn được thực hiện theo cùng một cách mỗi lần thông qua chương trình. Nếu giá trị float được chuyển đổi thành giá trị int tại một vị trí nhất định trong mã, giá trị float sẽ luôn được chuyển đổi thành giá trị int tại vị trí đó trong mã. Điều này không thể thay đổi trong thời gian chạy. Tất nhiên, chính giá trị có thể thay đổi từ một lần thực hiện chương trình này sang lần tiếp theo và các câu lệnh điều kiện có thể thay đổi phần mã nào được chạy theo thứ tự nào, nhưng một phần mã nhất định không có lệnh gọi hàm hoặc điều kiện sẽ luôn thực hiện chính xác hoạt động tương tự bất cứ khi nào nó được chạy.

  4. Tổng hợp có nghĩa là quá trình phân tích mã nguồn có thể đọc được của con người và chuyển đổi nó thành các hướng dẫn dễ đọc bằng máy được thực hiện đầy đủ trước khi chương trình chạy. Khi trình biên dịch đang biên dịch một hàm, nó không có kiến ​​thức về những gì nó sẽ gặp thêm trong một tệp nguồn đã cho. Tuy nhiên, khi quá trình biên dịch (và lắp ráp, liên kết, v.v.) hoàn thành, mỗi hàm trong tệp thực thi đã hoàn thành chứa con trỏ số tới các hàm mà nó sẽ gọi khi chạy. Đó là lý do tại sao main () có thể gọi một hàm xuống sâu hơn trong tệp nguồn. Vào thời điểm main () thực sự được chạy, nó sẽ chứa một con trỏ tới địa chỉ của Func_i ().

    Mã máy rất, rất cụ thể. Mã để thêm hai số nguyên (3 + 2) khác với mã để thêm hai số nổi (3.0 + 2.0). Cả hai đều khác nhau từ việc thêm một int vào một float (3 + 2.0), v.v. Trình biên dịch xác định cho mọi điểm trong hàm một thao tác chính xác cần phải được thực hiện tại điểm đó và tạo mã thực hiện thao tác chính xác đó. Một khi điều đó đã được thực hiện, nó không thể thay đổi mà không biên dịch lại hàm.

Đặt tất cả các khái niệm này lại với nhau, lý do mà main () không thể "nhìn" xuống để xác định loại Func_i () là phân tích loại xảy ra ngay từ đầu của quá trình biên dịch. Tại thời điểm đó, chỉ có phần của tệp nguồn theo định nghĩa của hàm main () đã được đọc và phân tích, và định nghĩa của Func_i () chưa được trình biên dịch biết.

Lý do mà main () có thể "thấy" trong đó Func_i () là để gọi nó là vì cuộc gọi đó xảy ra vào thời gian chạy, sau khi quá trình biên dịch đã giải quyết tất cả các tên và loại của tất cả các định danh, lắp ráp đã chuyển đổi tất cả các chức năng đối với mã máy và liên kết đã chèn đúng địa chỉ của từng chức năng ở mỗi nơi được gọi.

Tôi có, tất nhiên, bỏ qua hầu hết các chi tiết đẫm máu. Quá trình thực tế là nhiều, phức tạp hơn nhiều. Tôi hy vọng rằng tôi đã cung cấp đủ tổng quan cấp cao để trả lời câu hỏi của bạn.

Ngoài ra, hãy nhớ, những gì tôi đã viết ở trên đặc biệt áp dụng cho C.

Trong các ngôn ngữ khác, trình biên dịch có thể thực hiện nhiều lần chuyển qua mã nguồn và do đó trình biên dịch có thể nhận định nghĩa của Func_i () mà không bị khai báo trước.

Trong các ngôn ngữ khác, các hàm và / hoặc biến có thể được nhập động, do đó một biến duy nhất có thể giữ hoặc một hàm duy nhất có thể được truyền hoặc trả về, một số nguyên, float, chuỗi, mảng hoặc đối tượng tại các thời điểm khác nhau.

Trong các ngôn ngữ khác, gõ có thể mạnh hơn, yêu cầu chuyển đổi từ dấu phẩy động sang số nguyên để được chỉ định rõ ràng. Trong các ngôn ngữ khác, việc nhập có thể yếu hơn, cho phép chuyển đổi từ chuỗi "3.0" sang float 3.0 sang số 3 được thực hiện tự động.

Và trong các ngôn ngữ khác, mã có thể được giải thích một dòng tại một thời điểm, hoặc được biên dịch thành mã byte và sau đó được giải thích, hoặc được biên dịch đúng lúc, hoặc đưa vào một loạt các lược đồ thực thi khác.


1
Cảm ơn bạn đã trả lời tất cả trong một. Câu trả lời của bạn và của bạn là những gì tôi muốn biết. Ví dụ Func_()+1: ở đây tại thời gian biên dịch trình biên dịch biết loại Func_i()để tạo mã máy thích hợp. Có lẽ một trong hai nó không phải là có thể cho lắp ráp để xử lý Func_()+1bằng cách gọi các loại tại thời gian chạy, hoặc nó có thể nhưng làm như vậy sẽ làm cho chương trình chậm tại thời gian chạy. Tôi nghĩ rằng, nó là đủ cho tôi bây giờ.
dùng106313

1
Chi tiết quan trọng của các hàm được khai báo ngầm của C: Chúng được coi là thuộc loại int func(...)... tức là chúng có một danh sách đối số dao động. Điều này có nghĩa là nếu bạn định nghĩa một hàm là int putc(char)nhưng quên khai báo thì thay vào đó, nó sẽ được gọi là int putc(int)(vì char được chuyển qua danh sách đối số biến đổi được thăng cấp int). Vì vậy, trong khi ví dụ của OP tình cờ hoạt động vì chữ ký của nó khớp với tuyên bố ngầm, có thể hiểu được tại sao hành vi này không được khuyến khích (và cảnh báo thích hợp được thêm vào).
uliwitness

37

Một hạn chế thiết kế của ngôn ngữ C là nó được cho là được biên dịch bởi trình biên dịch một lượt, điều này làm cho nó phù hợp với các hệ thống rất hạn chế bộ nhớ. Do đó, trình biên dịch biết tại bất kỳ điểm nào chỉ về những thứ đã được đề cập trước đó. Trình biên dịch không thể bỏ qua trong nguồn để tìm một khai báo hàm và sau đó quay lại để biên dịch một cuộc gọi đến hàm đó. Do đó, tất cả các biểu tượng phải được khai báo trước khi chúng được sử dụng. Bạn có thể khai báo trước một hàm như

int Func_i();

ở đầu hoặc trong một tệp tiêu đề để giúp trình biên dịch.

Trong ví dụ của bạn, bạn sử dụng hai tính năng đáng ngờ của ngôn ngữ C nên tránh:

  1. Nếu một hàm được sử dụng trước khi nó được khai báo đúng, thì hàm này được sử dụng như một khai báo ngầm định của Google. Trình biên dịch sử dụng bối cảnh ngay lập tức để tìm ra chữ ký hàm. Trình biên dịch sẽ không quét qua phần còn lại của mã để tìm ra khai báo thực sự là gì.

  2. Nếu một cái gì đó được khai báo mà không có một loại, loại được thực hiện int. Đây là ví dụ cho trường hợp biến tĩnh hoặc kiểu trả về hàm.

Vì vậy, trong printf("func:%d",Func_i()), chúng tôi có một tuyên bố ngầm int Func_i(). Khi trình biên dịch đạt đến định nghĩa hàm Func_i() { ... }, điều này tương thích với kiểu. Nhưng nếu bạn đã viết float Func_i() { ... }vào thời điểm này, bạn có ý nghĩa được khai báo int Func_i()và tuyên bố rõ ràng float Func_i(). Vì hai khai báo không khớp nhau, trình biên dịch cung cấp cho bạn một lỗi.

Xóa bỏ một số quan niệm sai lầm

  • Trình biên dịch không tìm thấy giá trị được trả về bởi Func_i. Sự vắng mặt của một loại rõ ràng có nghĩa là loại trả về inttheo mặc định. Ngay cả khi bạn làm điều này:

    Func_i() {
        float f = 42.3;
        return f;
    }

    sau đó loại sẽ là int Func_i(), và giá trị trả lại sẽ bị cắt ngắn âm thầm!

  • Trình biên dịch cuối cùng đã biết loại thực Func_i, nhưng nó không biết loại thực trong quá trình khai báo ngầm. Chỉ khi nó đạt đến khai báo thực sự, nó mới có thể tìm ra loại khai báo ngầm là chính xác. Nhưng tại thời điểm đó, tập hợp cho lệnh gọi hàm có thể đã được viết và không thể thay đổi trong mô hình biên dịch C.


3
@ user31782: Thứ tự của mã quan trọng tại thời gian biên dịch, nhưng không phải lúc chạy. Trình biên dịch ra khỏi hình ảnh khi chương trình chạy. Theo thời gian chạy, chức năng sẽ được lắp ráp và liên kết, địa chỉ của nó sẽ được giải quyết và bị kẹt vào chỗ giữ địa chỉ của cuộc gọi. (Nó phức tạp hơn thế một chút, nhưng đó là ý tưởng cơ bản.) Bộ xử lý có thể phân nhánh tiến hoặc lùi.
Blrfl

20
@ user31782: Trình biên dịch không in giá trị. Trình biên dịch của bạn không chạy chương trình !!
Cuộc đua nhẹ nhàng với Monica

1
@LightnessRacesinOrbit Tôi biết điều đó. Tôi đã viết nhầm trình biên dịch trong bình luận của tôi ở trên vì tôi quên bộ xử lý tên .
dùng106313

3
@Carcigenicate C bị ảnh hưởng nặng nề bởi ngôn ngữ B, chỉ có một loại duy nhất: một kiểu số nguyên không có độ rộng từ cũng có thể được sử dụng cho con trỏ. C ban đầu đã sao chép hành vi này, nhưng giờ đây nó hoàn toàn bị cấm ngoài tiêu chuẩn C99. Unittạo ra một kiểu mặc định đẹp từ quan điểm lý thuyết kiểu, nhưng thất bại trong thực tiễn gần với lập trình hệ thống kim loại mà B và sau đó C được thiết kế cho.
amon

2
@ user31782: Trình biên dịch phải biết loại biến để tạo ra tổ hợp chính xác cho bộ xử lý. Khi trình biên dịch tìm thấy ẩn Func_i(), nó ngay lập tức tạo và lưu mã để bộ xử lý chuyển sang vị trí khác, sau đó nhận một số nguyên, rồi tiếp tục. Khi trình biên dịch sau đó tìm thấy Func_iđịnh nghĩa, nó sẽ đảm bảo các chữ ký khớp với nhau và nếu có, nó sẽ đặt cụm Func_i()tại địa chỉ đó và yêu cầu nó trả về một số nguyên. Khi bạn chạy chương trình, bộ xử lý sẽ làm theo các hướng dẫn đó với giá trị 3.
Vịt Mooing

10

Đầu tiên, các chương trình của bạn hợp lệ cho tiêu chuẩn C90, nhưng không phải cho các chương trình sau. ẩn int (cho phép khai báo hàm mà không đưa ra kiểu trả về của nó) và khai báo hàm ẩn (cho phép sử dụng hàm mà không khai báo hàm) sẽ không còn hợp lệ.

Thứ hai, điều đó không hoạt động như bạn nghĩ.

  1. Loại kết quả là tùy chọn trong C90, không đưa ra một nghĩa là intkết quả. Điều đó cũng đúng với khai báo biến (nhưng bạn phải cung cấp một lớp lưu trữ, statichoặc extern).

  2. Trình biên dịch làm gì khi nhìn thấy Func_iđược gọi mà không có khai báo trước, giả sử rằng có một khai báo

    extern int Func_i();

    Nó không nhìn xa hơn trong mã để xem cách Func_ikhai báo hiệu quả . Nếu Func_ikhông được khai báo hoặc định nghĩa, trình biên dịch sẽ không thay đổi hành vi của nó khi biên dịch main. Khai báo ngầm chỉ dành cho hàm, không có biến nào.

    Lưu ý rằng danh sách tham số trống trong khai báo không có nghĩa là hàm không lấy tham số (bạn cần chỉ định (void)cho điều đó), điều đó có nghĩa là trình biên dịch không phải kiểm tra các loại tham số và sẽ giống nhau chuyển đổi ngầm định được áp dụng cho các đối số được truyền cho các hàm matrixdic.


Nếu trình biên dịch có thể tìm thấy giá trị được trả về bởi Func_i () bằng cách ra khỏi hàm main () và tìm kiếm mã nguồn, thì tại sao nó không thể tìm thấy loại Func_i (), được đề cập rõ ràng.
dùng106313

1
@ user31782 Nếu không có khai báo trước về Func_i, khi thấy Func_i được sử dụng trong biểu thức cuộc gọi, hãy cư xử như thể có một extern int Func_i(). Nó không nhìn đâu cả.
AProgrammer 23/12/14

1
@ user31782, trình biên dịch không nhảy ở bất cứ đâu. Nó sẽ phát ra mã để gọi chức năng đó; giá trị trả về sẽ được xác định tại thời điểm chạy. Chà, trong trường hợp một hàm đơn giản như vậy có trong cùng một đơn vị biên dịch, giai đoạn tối ưu hóa có thể nội tuyến hàm, nhưng đó không phải là điều bạn nên nghĩ đến khi xem xét các quy tắc của ngôn ngữ, đó là một tối ưu hóa.
AProgrammer 23/12/14

10
@ user31782, bạn có những hiểu lầm nghiêm trọng về cách các chương trình hoạt động. Nghiêm trọng đến mức tôi không nghĩ p.se là một nơi tốt để sửa chúng (có lẽ là trò chuyện, nhưng tôi sẽ không thử làm điều đó).
AProgrammer 23/12/14

1
@ user31782: Viết một đoạn mã nhỏ và biên dịch nó với -S(nếu bạn đang sử dụng gcc) sẽ cho phép bạn xem mã lắp ráp được tạo bởi trình biên dịch. Sau đó, bạn có thể có ý tưởng về cách xử lý các giá trị trả về trong thời gian chạy (thông thường sử dụng thanh ghi bộ xử lý hoặc một số khoảng trống trên ngăn xếp chương trình).
Giorgio

7

Bạn đã viết trong một bình luận:

Việc thực hiện được thực hiện từng dòng một. Cách duy nhất để tìm giá trị được Func_i () trả về là nhảy ra khỏi chính

Đó là một quan niệm sai lầm: Thi hành không phải là từng dòng một. Quá trình biên dịch được thực hiện theo từng dòng và việc phân giải tên được thực hiện trong quá trình biên dịch và nó chỉ giải quyết các tên, không trả về các giá trị.

Một mô hình khái niệm hữu ích là thế này: Khi trình biên dịch đọc dòng:

  printf("func:%d",Func_i());

nó phát ra mã tương đương với:

  1. call "function #2" and put the return value on the stack
  2. put the constant string "func:%d" on the stack
  3. call "function #1"

Trình biên dịch cũng tạo một ghi chú trong một số bảng nội bộ function #2là một hàm chưa được khai báo có tên Func_i, lấy một số lượng đối số không xác định và trả về một int (mặc định).

Sau đó, khi nó phân tích cú pháp này:

 int Func_i() { ...

trình biên dịch tra cứu Func_itrong bảng được đề cập ở trên và kiểm tra xem các tham số và kiểu trả về có khớp không. Nếu họ không, nó dừng lại với một thông báo lỗi. Nếu họ làm như vậy, nó sẽ thêm địa chỉ hiện tại vào bảng chức năng nội bộ và chuyển sang dòng tiếp theo.

Vì vậy, trình biên dịch đã không "tìm kiếm" Func_ikhi phân tích cú pháp tham chiếu đầu tiên. Nó chỉ đơn giản là ghi chú trong một số bảng, tiếp tục phân tích cú pháp dòng tiếp theo. Và ở cuối tập tin, nó có một tệp đối tượng và một danh sách các địa chỉ nhảy.

Sau đó, trình liên kết lấy tất cả điều này và thay thế tất cả các con trỏ thành "hàm số 2" bằng địa chỉ bước nhảy thực tế, do đó, nó phát ra một cái gì đó như:

  call 0x0001215 and put the result on the stack
  put constant ... on the stack
  call ...
...
[at offset 0x0001215 in the file, compiled result of Func_i]:
  put 3 on the stack
  return top of the stack

Rất lâu sau, khi tệp thực thi được chạy, địa chỉ bước nhảy đã được giải quyết và máy tính chỉ có thể nhảy đến địa chỉ 0x1215. Không cần tra cứu tên.

Tuyên bố miễn trừ trách nhiệm : Như tôi đã nói, đó là một mô hình khái niệm và thế giới thực phức tạp hơn. Trình biên dịch và trình liên kết thực hiện tất cả các loại tối ưu hóa điên rồ ngày nay. Họ thậm chí có thể "nhảy xuống" để tìm kiếm Func_i, mặc dù tôi nghi ngờ điều đó. Nhưng các ngôn ngữ C được định nghĩa theo cách mà bạn có thể viết một trình biên dịch siêu đơn giản như thế. Vì vậy, hầu hết thời gian, nó là một mô hình rất hữu ích.


Cảm ơn về câu trả lời của bạn. Trình biên dịch không thể phát mã:1. call "function #2", put the return-type onto the stack and put the return value on the stack?
user106313

1
(. Tiếp Theo) Ngoài ra: Nếu bạn đã viết printf(..., Func_i()+1);- trình biên dịch biết các loại Func_i, vì vậy nó có thể quyết định nếu nó được thải một add integerhoặc một add floathướng dẫn. Bạn có thể tìm thấy một số trường hợp đặc biệt trong đó trình biên dịch có thể tiếp tục mà không có thông tin loại, nhưng trình biên dịch phải hoạt động cho tất cả các trường hợp.
nikie

4
@ user31782: Hướng dẫn về máy, theo quy tắc, rất đơn giản: Thêm hai thanh ghi số nguyên 32 bit. Tải một địa chỉ bộ nhớ vào một thanh ghi số nguyên 16 bit. Chuyển đến một địa chỉ. Ngoài ra, không có loại : Bạn có thể vui vẻ tải một vị trí bộ nhớ đại diện cho số float 32 bit vào một thanh ghi số nguyên 32 bit và thực hiện một số số học với nó. (Nó hiếm khi có ý nghĩa.) Vì vậy, không, bạn không thể phát trực tiếp mã máy như thế. Bạn có thể viết một trình biên dịch thực hiện tất cả những điều đó với kiểm tra thời gian chạy và dữ liệu loại thêm trên ngăn xếp. Nhưng nó sẽ không phải là trình biên dịch C.
nikie

1
@ user31782: Phụ thuộc, IIRC. floatcác giá trị có thể sống trong một thanh ghi FPU - sau đó sẽ không có hướng dẫn nào cả. Trình biên dịch chỉ theo dõi giá trị nào được lưu trữ trong đó đăng ký trong quá trình biên dịch và phát ra các thứ như "thêm hằng số 1 vào thanh ghi X". Hoặc nó có thể sống trên stack, nếu không có thanh ghi miễn phí. Sau đó, sẽ có hướng dẫn "tăng con trỏ ngăn xếp lên 4" và giá trị sẽ được "tham chiếu" giống như "con trỏ ngăn xếp - 4". Nhưng tất cả những điều này chỉ hoạt động nếu kích thước của tất cả các biến (trước và sau) trên ngăn xếp được biết tại thời gian biên dịch.
nikie

1
Từ tất cả các cuộc thảo luận tôi đã đạt đến sự hiểu biết này: Để trình biên dịch tạo mã lắp ráp hợp lý cho bất kỳ câu lệnh nào bao gồm Func_i()hoặc / và Data_i, nó phải xác định các kiểu của chúng; trong ngôn ngữ lắp ráp không thể thực hiện cuộc gọi đến kiểu dữ liệu. Tôi cần nghiên cứu chi tiết bản thân mình để được đảm bảo.
dùng106313

5

C và một số ngôn ngữ khác yêu cầu khai báo được thiết kế trong thời đại mà thời gian và bộ nhớ của bộ xử lý đắt đỏ. Sự phát triển của C và Unix đã song hành trong một thời gian và sau này không có bộ nhớ ảo cho đến khi 3BSD xuất hiện vào năm 1979. Không có thêm phòng để làm việc, các trình biên dịch có xu hướng là một công việc đơn lẻ vì chúng không yêu cầu khả năng giữ một số đại diện của toàn bộ tệp trong bộ nhớ cùng một lúc.

Trình biên dịch một lượt, giống như chúng ta, không có khả năng nhìn thấy tương lai. Điều này có nghĩa là những điều duy nhất họ có thể biết chắc chắn là những gì họ đã được nói rõ ràng trước khi dòng mã được biên dịch. Thật đơn giản đối với một trong hai chúng ta Func_i()được khai báo sau trong tệp nguồn, nhưng trình biên dịch, hoạt động trên một đoạn mã nhỏ tại một thời điểm, không có manh mối nào.

Vào đầu C (AT & T, K & R, C89), việc sử dụng hàm foo()trước khi khai báo dẫn đến khai báo thực tế hoặc ẩn int foo(). Ví dụ của bạn hoạt động khi Func_i()được khai báo intvì nó khớp với những gì trình biên dịch đã khai báo thay cho bạn. Thay đổi nó thành bất kỳ loại nào khác sẽ dẫn đến xung đột vì nó không còn phù hợp với những gì trình biên dịch đã chọn trong trường hợp không có tuyên bố rõ ràng. Hành vi này đã bị xóa trong C99, trong đó việc sử dụng chức năng không được khai báo đã trở thành một lỗi.

Vậy những gì về loại trả lại?

Quy ước gọi mã đối tượng trong hầu hết các môi trường yêu cầu chỉ biết địa chỉ của hàm được gọi, tương đối dễ dàng cho trình biên dịch và trình liên kết để xử lý. Thực thi nhảy đến khi bắt đầu chức năng và quay trở lại khi nó trở lại. Bất cứ điều gì khác, đáng chú ý là sắp xếp chuyển các đối số và giá trị trả về, được xác định hoàn toàn bởi người gọi và callee trong một sắp xếp được gọi là quy ước gọi . Miễn là cả hai chia sẻ cùng một tập hợp các quy ước, chương trình có thể gọi các hàm trong các tệp đối tượng khác cho dù chúng được biên dịch theo bất kỳ ngôn ngữ nào có chung các quy ước đó. (Trong điện toán khoa học, bạn gặp rất nhiều C gọi FORTRAN và ngược lại, và khả năng thực hiện điều đó xuất phát từ việc có một quy ước gọi.)

Một đặc điểm khác của đầu C là các nguyên mẫu mà chúng ta biết hiện tại chúng không tồn tại. Bạn có thể khai báo kiểu trả về của hàm số (ví dụ int foo()), nhưng không đối số của nó (tức là, int foo(int bar)không phải là một lựa chọn). Điều này tồn tại bởi vì, như đã nêu ở trên, chương trình luôn bị mắc kẹt vào một quy ước gọi có thể được xác định bởi các đối số. Nếu bạn đã gọi một hàm với loại đối số sai, thì đó là tình huống rác, rác ra.

Vì mã đối tượng có khái niệm trả về nhưng không phải là kiểu trả về, nên trình biên dịch phải biết kiểu trả về để xử lý giá trị được trả về. Khi bạn đang chạy hướng dẫn máy, tất cả chỉ là bit và bộ xử lý không quan tâm liệu bộ nhớ mà bạn đang cố so sánh có doublethực sự có inttrong đó hay không. Nó chỉ làm những gì bạn yêu cầu, và nếu bạn phá vỡ nó, bạn sở hữu cả hai mảnh.

Hãy xem xét các bit của mã:

double foo();         double foo();
double x;             int x;
x = foo();            x = foo();

Mã ở bên trái biên dịch thành một cuộc gọi để foo()theo sau bằng cách sao chép kết quả được cung cấp thông qua quy ước gọi / trả lại vào bất cứ nơi nào xđược lưu trữ. Đó là trường hợp dễ dàng.

Mã bên phải hiển thị một chuyển đổi loại và là lý do tại sao trình biên dịch cần biết loại trả về của hàm. Các số dấu phẩy động không thể được đổ vào bộ nhớ trong đó các mã khác sẽ thấy intvì không có chuyển đổi ma thuật nào diễn ra. Nếu kết quả cuối cùng phải là một số nguyên, thì phải có hướng dẫn hướng dẫn bộ xử lý thực hiện chuyển đổi trước khi lưu trữ. Nếu không biết kiểu trả về foo()trước thời hạn, trình biên dịch sẽ không biết rằng mã chuyển đổi là cần thiết.

Trình biên dịch nhiều lượt cho phép tất cả các loại, một trong số đó là khả năng khai báo các biến, hàm và phương thức sau khi chúng được sử dụng lần đầu tiên. Điều này có nghĩa là khi trình biên dịch bắt đầu biên dịch mã, nó đã nhìn thấy tương lai và biết phải làm gì. Java, ví dụ, bắt buộc đa thông qua thực tế là cú pháp của nó cho phép khai báo sau khi sử dụng.


Cảm ơn câu trả lời của bạn (+1). Tôi không biết chi tiết về hoạt động bên trong của trình biên dịch, trình biên dịch, bộ xử lý, v.v. Ý tưởng cơ bản của câu hỏi của tôi là nếu tôi nói (ghi) giá trị trả về của hàm trong mã nguồn cuối cùng, sau khi sử dụng của chức năng đó thì ngôn ngữ cho phép máy tính tìm giá trị đó mà không đưa ra bất kỳ lỗi nào. Bây giờ tại sao máy tính không thể tìm thấy loại tương tự. Tại sao loại Data_i không thể được tìm thấy dưới dạng Func_i()giá trị trả về được tìm thấy.
dùng106313

Tôi vẫn chưa hài lòng. double foo(); int x; x = foo();chỉ đơn giản là đưa ra lỗi. Tôi biết rằng chúng ta không thể làm điều này. Câu hỏi của tôi là trong hàm gọi bộ xử lý chỉ tìm giá trị trả về; Tại sao nó cũng không thể tìm thấy kiểu trả về?
dùng106313

1
@ user31782: Không nên. Có một nguyên mẫu cho foo(), vì vậy trình biên dịch biết phải làm gì với nó.
Blrfl

2
@ user31782: Bộ xử lý không có bất kỳ khái niệm nào về kiểu trả về.
Blrfl

1
@ user31782 Đối với câu hỏi về thời gian biên dịch: Có thể viết một ngôn ngữ trong đó tất cả các phân tích loại này có thể được thực hiện tại thời gian biên dịch. C không phải là một ngôn ngữ như vậy. Trình biên dịch C không thể làm điều đó bởi vì nó không được thiết kế để làm điều đó. Nó có thể được thiết kế khác nhau? Chắc chắn, nhưng sẽ cần nhiều sức mạnh xử lý và bộ nhớ để làm như vậy. Điểm mấu chốt là không. Nó được thiết kế theo cách mà các máy tính thời đó có khả năng xử lý tốt nhất.
Mr.Mindor 23/12/14
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.