Có phải các công cụ khai báo kiểu dữ liệu như là int int và các bộ dữ liệu được lưu trữ trong RAM khi chương trình C thực thi không?


74

Khi một chương trình C đang chạy, dữ liệu được lưu trữ trên heap hoặc stack. Các giá trị được lưu trữ trong địa chỉ RAM. Nhưng những gì về các chỉ số loại (ví dụ, inthoặc char)? Họ cũng được lưu trữ?

Hãy xem xét các mã sau đây:

char a = 'A';
int x = 4;

Tôi đọc rằng A và 4 được lưu trữ trong các địa chỉ RAM ở đây. Nhưng những gì về ax? Khó hiểu nhất, làm thế nào để thực thi biết đó alà một char và xlà một int? Ý tôi là, intcharđược đề cập ở đâu đó trong RAM?

Giả sử một giá trị được lưu trữ ở đâu đó trong RAM là 10011001; Nếu tôi là chương trình thực thi mã, làm sao tôi biết 10011001 này là a charhay an int?

Điều tôi không hiểu là làm thế nào máy tính biết được, khi nó đọc giá trị của một biến từ một địa chỉ như 10001, cho dù đó là inthay char. Hãy tưởng tượng tôi nhấp vào một chương trình được gọi là anyprog.exe. Ngay lập tức mã bắt đầu thực thi. Tập tin thực thi này có bao gồm thông tin về việc các biến được lưu trữ thuộc loại inthay charkhông?


24
Thông tin này hoàn toàn bị mất vào thời gian chạy. Bạn (và trình biên dịch của bạn) phải đảm bảo trước rằng bộ nhớ sẽ được giải thích chính xác. Đây có phải là câu trả lời bạn sau?
5gon12eder

4
Nó không. Bởi vì nó giả định rằng bạn biết những gì bạn đang làm, nó sẽ lấy bất cứ thứ gì nó tìm thấy tại địa chỉ bộ nhớ mà bạn cung cấp và ghi nó vào thiết bị xuất chuẩn. Nếu bất cứ điều gì được viết tương ứng với một nhân vật có thể đọc được, cuối cùng nó sẽ hiển thị trên bảng điều khiển của ai đó dưới dạng một nhân vật có thể đọc được. Nếu nó không tương ứng, nó sẽ xuất hiện dưới dạng vô nghĩa hoặc có thể là một ký tự ngẫu nhiên có thể đọc được.
Robert Harvey

22
@ user16307 Câu trả lời ngắn gọn là trong các ngôn ngữ được nhập tĩnh, bất cứ khi nào bạn in ra một char, trình biên dịch sẽ tạo ra các mã khác với cách in ra một int. Trong thời gian chạy không còn bất kỳ kiến ​​thức nào xlà char, mà đó là mã in char được chạy, bởi vì đó là những gì trình biên dịch đã chọn.
Ixrec

13
@ user16307 Nó luôn được lưu trữ dưới dạng biểu diễn nhị phân của số 65. Việc nó được in ra là 65 hay A phụ thuộc vào mã mà trình biên dịch của bạn tạo ra để in ra. Không có siêu dữ liệu nào bên cạnh số 65 nói rằng nó thực sự là một char hoặc int (ít nhất, không phải bằng các ngôn ngữ được nhập tĩnh như C).
Ixrec

2
Hiểu đầy đủ các khái niệm bạn yêu cầu ở đây và tự mình thực hiện chúng, bạn có thể muốn tham gia một khóa học về trình biên dịch, ví dụ như của
coursera

Câu trả lời:


122

Để giải quyết câu hỏi bạn đã đăng trong một số bình luận (mà tôi nghĩ bạn nên chỉnh sửa thành bài đăng của mình):

Điều tôi không hiểu là làm thế nào máy tính biết cho phép khi nó đọc giá trị của biến và địa chỉ như 10001 nếu là int hoặc char. Hãy tưởng tượng tôi nhấp vào một chương trình gọi là anyprog.exe. Ngay lập tức mã bắt đầu thực thi. Tập tin exe này có bao gồm thông tin về việc các biến được lưu trữ như trong hoặc char không?

Vì vậy, hãy đặt một số mã cho nó. Hãy nói rằng bạn viết:

int x = 4;

Và hãy giả sử rằng nó được lưu trữ trong RAM:

0x00010004: 0x00000004

Phần đầu tiên là địa chỉ, phần thứ hai là giá trị. Khi chương trình của bạn (thực thi dưới dạng mã máy) chạy, tất cả những gì nó thấy 0x00010004là giá trị 0x000000004. Nó không 'biết' loại dữ liệu này và cũng không biết nó được sử dụng như thế nào.

Vì vậy, làm thế nào để chương trình của bạn tìm ra điều đúng đắn để làm? Hãy xem xét mã này:

int x = 4;
x = x + 5;

Chúng tôi có một đọc và viết ở đây. Khi chương trình của bạn đọc xtừ bộ nhớ, nó tìm thấy 0x00000004ở đó. Và chương trình của bạn biết để thêm 0x00000005vào nó. Và lý do chương trình của bạn 'biết' đây là một hoạt động hợp lệ, là bởi vì trình biên dịch đảm bảo rằng hoạt động đó là hợp lệ thông qua loại an toàn. Trình biên dịch của bạn đã xác minh rằng bạn có thể thêm 45cùng nhau. Vì vậy, khi mã nhị phân của bạn chạy (exe), nó không phải thực hiện xác minh đó. Nó chỉ thực hiện từng bước một cách mù quáng, giả sử mọi thứ đều ổn (những điều tồi tệ xảy ra khi chúng thực tế, không ổn).

Một cách khác để nghĩ về nó là như thế này. Tôi cung cấp cho bạn thông tin này:

0x00000004: 0x12345678

Cùng định dạng như trước - địa chỉ bên trái, giá trị bên phải. Loại nào là giá trị? Tại thời điểm này, bạn biết nhiều thông tin về giá trị đó như máy tính của bạn khi thực thi mã. Nếu tôi bảo bạn thêm 12743 vào giá trị đó, bạn có thể làm điều đó. Bạn không biết tác động của hoạt động đó sẽ là gì trên toàn hệ thống, nhưng thêm hai số là điều bạn thực sự giỏi, vì vậy bạn có thể thực hiện nó. Điều đó làm cho giá trị một int? Không nhất thiết - Tất cả những gì bạn thấy là hai giá trị 32 bit và toán tử cộng.

Có lẽ một số nhầm lẫn sau đó nhận được dữ liệu trở lại. Nếu chúng ta có:

char A = 'a';

Làm thế nào để máy tính biết hiển thị atrong giao diện điều khiển? Vâng, có rất nhiều bước để đó. Đầu tiên là đi đến Avị trí của bộ nhớ và đọc nó:

0x00000004: 0x00000061

Giá trị hex cho aASCII là 0x61, do đó, ở trên có thể là thứ bạn thấy trong bộ nhớ. Vì vậy, bây giờ mã máy của chúng tôi biết giá trị số nguyên. Làm thế nào để nó biết để biến giá trị số nguyên thành một ký tự để hiển thị nó? Nói một cách đơn giản, trình biên dịch đảm bảo đưa vào tất cả các bước cần thiết để thực hiện quá trình chuyển đổi đó. Nhưng chính máy tính của bạn (hoặc chương trình / exe) không biết loại dữ liệu đó là gì. Giá trị 32 bit đó có thể là bất cứ thứ gì - int,, charmột nửa của doublemột con trỏ, một phần của một mảng, một phần của một stringphần của một lệnh, v.v.


Đây là một tương tác ngắn mà chương trình của bạn (exe) có thể có với máy tính / hệ điều hành.

Chương trình: Tôi muốn khởi nghiệp. Tôi cần 20 MB bộ nhớ.

Hệ điều hành: tìm thấy 20 MB bộ nhớ miễn phí không sử dụng và trao chúng

(Các lưu ý quan trọng là điều này có thể trở lại bất kỳ 20 MB bộ nhớ, họ thậm chí không phải là tiếp giáp. Tại thời điểm này, chương trình bây giờ có thể hoạt động trong bộ nhớ nó có mà không nói chuyện với các hệ điều hành)

Chương trình: Tôi sẽ giả định rằng vị trí đầu tiên trong bộ nhớ là biến số nguyên 32 bit x.

(Trình biên dịch đảm bảo rằng việc truy cập vào các biến khác sẽ không bao giờ chạm vào điểm này trong bộ nhớ. Không có gì trên hệ thống cho biết byte đầu tiên là biến xhoặc biến đó xlà một số nguyên. Tương tự: bạn có một túi. Bạn nói với mọi người rằng bạn sẽ chỉ để những quả bóng màu vàng trong chiếc túi này. Khi ai đó sau đó rút thứ gì đó ra khỏi túi, thì sẽ rất sốc khi họ lấy ra thứ gì đó màu xanh hoặc hình khối - một cái gì đó đã sai lầm khủng khiếp. chương trình hiện đang giả sử vị trí bộ nhớ đầu tiên là biến x và đó là một số nguyên. Nếu một thứ khác được ghi trên byte bộ nhớ này hoặc nó được coi là một thứ khác - điều gì đó khủng khiếp đã xảy ra. Trình biên dịch đảm bảo các loại điều này không xảy ra. không xảy ra)

Chương trình: Bây giờ tôi sẽ ghi 2vào bốn byte đầu tiên mà tôi giả sử xlà tại.

Chương trình: Tôi muốn thêm 5 vào x.

  • Đọc giá trị của X vào một thanh ghi tạm thời

  • Thêm 5 vào sổ đăng ký tạm thời

  • Lưu trữ giá trị của thanh ghi tạm thời trở lại byte đầu tiên, vẫn được giả sử là x.

Chương trình: Tôi sẽ giả sử byte có sẵn tiếp theo là biến char y.

Chương trình: Tôi sẽ viết avào biến y.

  • Một thư viện được sử dụng để tìm giá trị byte cho a

  • Byte được ghi vào địa chỉ mà chương trình giả định là y.

Chương trình: Tôi muốn hiển thị nội dung của y

  • Đọc giá trị ở vị trí bộ nhớ thứ hai

  • Sử dụng một thư viện để chuyển đổi từ byte thành ký tự

  • Sử dụng các thư viện đồ họa để thay đổi màn hình bảng điều khiển (cài đặt pixel từ đen sang trắng, cuộn một dòng, v.v.)

(Và nó tiếp tục từ đây)

Những gì bạn có thể bị treo lên là - điều gì xảy ra khi vị trí đầu tiên trong bộ nhớ không còn nữa x? hay cái thứ hai không còn nữa y? Điều gì xảy ra khi ai đó đọc xnhư một charhoặc ymột con trỏ? Trong ngắn hạn, những điều xấu xảy ra. Một số trong những điều này có hành vi được xác định rõ, và một số có hành vi không xác định. Hành vi không xác định chính xác là như vậy - bất cứ điều gì cũng có thể xảy ra, từ không có gì, đến việc làm hỏng chương trình hoặc hệ điều hành. Ngay cả hành vi được xác định rõ cũng có thể độc hại. Nếu tôi có thể thay đổi xthành một con trỏ tới chương trình của mình và để chương trình của bạn sử dụng nó làm con trỏ, thì tôi có thể khiến chương trình của bạn bắt đầu thực hiện chương trình của mình - đó chính xác là những gì tin tặc làm. Trình biên dịch có mặt để giúp đảm bảo chúng tôi không sử dụng int xnhư mộtstringvà những thứ thuộc về bản chất đó Bản thân mã máy không biết các loại và nó sẽ chỉ làm những gì hướng dẫn bảo nó làm. Ngoài ra còn có một lượng lớn thông tin được phát hiện vào thời gian chạy: byte nào của bộ nhớ là chương trình được phép sử dụng? Có xbắt đầu ở byte đầu tiên hoặc ngày 12 không?

Nhưng bạn có thể tưởng tượng sẽ kinh khủng thế nào khi thực sự viết các chương trình như thế này (và bạn có thể, bằng ngôn ngữ lắp ráp). Bạn bắt đầu bằng cách 'khai báo' các biến của mình - bạn tự nói với mình rằng byte 1 là x, byte 2 là yvà khi bạn viết từng dòng mã, tải và lưu trữ các thanh ghi, bạn (như một con người) phải nhớ đó là xcái nào và cái nào một là y, bởi vì hệ thống không có ý tưởng. Và bạn (như một con người) phải nhớ những loại xylà gì, bởi vì một lần nữa - hệ thống không có ý tưởng.


Giải thích tuyệt vời. Chỉ phần bạn đã viết "Làm thế nào để biết biến giá trị nguyên thành ký tự để hiển thị nó? Đơn giản chỉ cần đặt, trình biên dịch đảm bảo đưa vào tất cả các bước cần thiết để thực hiện chuyển đổi đó." vẫn còn sương mù đối với tôi. Hãy nói rằng CPU đã tải 0x00000061 từ thanh ghi RAM. Từ thời điểm này, bạn có nói rằng có các hướng dẫn khác (trong tệp exe) thực hiện chuyển đổi đó sang những gì chúng ta thấy trên màn hình không?
dùng16307

2
@ user16307 có, có hướng dẫn bổ sung. Mỗi dòng mã bạn viết có khả năng được chuyển thành nhiều hướng dẫn. Có hướng dẫn để tìm ra nhân vật nào sẽ sử dụng, có hướng dẫn về việc thay đổi pixel nào và màu sắc họ thay đổi, v.v. Ngoài ra còn có mã mà bạn không thực sự nhìn thấy. Ví dụ: sử dụng std :: cout có nghĩa là bạn đang sử dụng thư viện. Mã của bạn để ghi vào bàn điều khiển có thể chỉ là một dòng, nhưng (các) chức năng bạn gọi sẽ có nhiều dòng hơn và mỗi dòng có thể biến thành nhiều hướng dẫn máy.
Shaz

8
@ user16307 Otherwise how can console or text file outputs a character instead of int Bởi vì có một chuỗi hướng dẫn khác nhau để xuất nội dung của một vị trí bộ nhớ dưới dạng số nguyên hoặc dưới dạng ký tự chữ và số. Trình biên dịch biết về các loại biến và chọn chuỗi hướng dẫn thích hợp tại thời điểm biên dịch và ghi lại nó trong EXE.
Charles E. Grant

2
Tôi sẽ tìm một cụm từ khác cho "Bản thân mã byte", vì mã byte (hoặc mã byte) thường dùng để chỉ một ngôn ngữ trung gian (như Mã byte Java hoặc MSIL), có thể thực sự lưu trữ dữ liệu này cho thời gian chạy để tận dụng. Thêm vào đó, nó không hoàn toàn rõ ràng "mã byte" được đề cập đến trong bối cảnh đó. Nếu không, câu trả lời tốt đẹp.
jpmc26

6
@ user16307 Hãy cố gắng đừng lo lắng về C ++ và C #. Những gì những người này đang nói là vượt xa sự hiểu biết hiện tại của bạn về cách máy tính và trình biên dịch hoạt động. Đối với mục đích của những gì bạn đang cố gắng để hiểu, phần cứng KHÔNG biết gì về các loại, char hoặc int hoặc bất cứ điều gì. Khi bạn nói với trình biên dịch, một số biến là một int, nó tạo ra mã thực thi để xử lý một vị trí bộ nhớ NHƯ đó là một int. Vị trí bộ nhớ không chứa thông tin về các loại; chỉ là chương trình của bạn quyết định coi nó như một int. Quên mọi thứ khác bạn đã nghe về thông tin loại thời gian chạy.
Andres F.

43

Tôi nghĩ rằng câu hỏi chính của bạn dường như là: "Nếu loại bị xóa trong thời gian biên dịch và không được giữ lại trong thời gian chạy, thì làm thế nào để máy tính biết liệu thực thi mã có diễn giải nó như là một inthoặc thực thi mã mà diễn giải nó thành một char? "

Và câu trả lời là máy tính không. Tuy nhiên, trình biên dịch không biết, và nó sẽ chỉ đơn giản là đặt mã chính xác trong nhị phân ở nơi đầu tiên. Nếu biến được gõ là char, thì trình biên dịch sẽ không đặt mã để coi nó là một inttrong chương trình, nó sẽ đặt mã để xử lý nó là a char.

nhiều lý do để giữ lại kiểu khi chạy:

  • Gõ động: trong kiểu gõ động, kiểm tra kiểu xảy ra trong thời gian chạy, vì vậy, rõ ràng, kiểu phải được biết khi chạy. Nhưng C không được gõ động, vì vậy các loại có thể được xóa một cách an toàn. (Lưu ý rằng đây là một trường hợp rất khác nhau. Tuy nhiên, Kiểu động và Kiểu tĩnh không thực sự giống nhau và trong ngôn ngữ gõ hỗn hợp, bạn vẫn có thể xóa các kiểu tĩnh và chỉ giữ các kiểu động.)
  • Đa hình động: nếu bạn thực thi các mã khác nhau dựa trên loại thời gian chạy, thì bạn cần giữ loại thời gian chạy xung quanh. C không có đa hình động (thực sự không có bất kỳ đa hình nào, ngoại trừ trong một số trường hợp mã hóa đặc biệt, ví dụ +toán tử), vì vậy nó không cần loại thời gian chạy vì lý do đó. Tuy nhiên, một lần nữa, kiểu thời gian chạy là một cái gì đó khác với kiểu tĩnh, ví dụ như trong Java, về mặt lý thuyết bạn có thể xóa các kiểu tĩnh và vẫn giữ kiểu thời gian chạy cho đa hình. Cũng lưu ý rằng, nếu bạn phân cấp và chuyên môn hóa mã tra cứu kiểu và đặt nó vào bên trong đối tượng (hoặc lớp), thì bạn cũng không nhất thiết cần loại thời gian chạy, ví dụ như vtables C ++.
  • Reflection Runtime: nếu bạn cho phép chương trình phản ánh về các kiểu của nó khi chạy, thì rõ ràng bạn cần phải giữ các kiểu khi chạy. Bạn có thể dễ dàng thấy điều này với Java, nó giữ các kiểu bậc nhất trong thời gian chạy, nhưng xóa các đối số kiểu thành các kiểu chung trong thời gian biên dịch, do đó bạn chỉ có thể phản ánh trên hàm tạo kiểu ("kiểu thô") chứ không phải đối số kiểu. Một lần nữa, C không có phản xạ thời gian chạy, do đó không cần giữ kiểu khi chạy.

Lý do duy nhất để giữ kiểu trong thời gian chạy trong C sẽ là để gỡ lỗi, tuy nhiên, việc gỡ lỗi thường được thực hiện với nguồn có sẵn, và sau đó bạn có thể chỉ cần tìm loại trong tệp nguồn.

Loại Erasure khá bình thường. Nó không ảnh hưởng đến an toàn loại: các loại được kiểm tra tại thời điểm biên dịch, một khi trình biên dịch hài lòng rằng chương trình là loại an toàn, các loại không còn cần thiết (vì lý do đó). Nó không ảnh hưởng đến đa hình tĩnh (hay còn gọi là quá tải): khi quá trình giải quyết quá tải hoàn tất và trình biên dịch đã chọn đúng mức quá tải, nó không cần các kiểu nữa. Các loại cũng có thể hướng dẫn tối ưu hóa, nhưng một lần nữa, một khi trình tối ưu hóa đã chọn tối ưu hóa dựa trên các loại, nó không cần chúng nữa.

Giữ lại các loại trong thời gian chạy chỉ được yêu cầu khi bạn muốn làm một cái gì đó với các loại trong thời gian chạy.

Haskell là một trong những ngôn ngữ gõ tĩnh nghiêm ngặt nhất, nghiêm ngặt nhất, an toàn loại và trình biên dịch Haskell thường xóa tất cả các loại. (Tôi tin rằng ngoại lệ là việc chuyển từ điển phương thức cho các lớp loại.)


3
Không! Tại sao? Thông tin đó sẽ cần thiết cho cái gì? Trình biên dịch xuất mã để đọc a charvào tệp nhị phân đã biên dịch. Nó không ra mã cho một int, nó không ra mã cho một byte, nó không ra mã cho một con trỏ, nó chỉ đơn giản là kết quả đầu ra chỉ mã cho một char. Không có quyết định thời gian chạy được thực hiện dựa trên loại. Bạn không cần loại. Nó là hoàn toàn và hoàn toàn không liên quan. Tất cả các quyết định liên quan đã được đưa ra tại thời điểm biên dịch.
Jörg W Mittag

2
Không có. Trình biên dịch chỉ cần đặt mã để in một char trong tệp nhị phân. Giai đoạn = Stage. Trình biên dịch biết rằng tại địa chỉ bộ nhớ đó, có char, do đó nó đặt mã để in một char trong tệp nhị phân. Nếu giá trị tại địa chỉ bộ nhớ đó vì một lý do kỳ lạ nào đó không phải là một char, thì, tất cả, địa ngục vỡ ra. Về cơ bản, đó là cách mà cả một lớp khai thác bảo mật hoạt động.
Jörg W Mittag

2
Hãy nghĩ về nó: nếu CPU bằng cách nào đó biết về các loại chương trình dữ liệu, thì mọi người trên hành tinh sẽ phải mua CPU mới mỗi khi ai đó phát minh ra một loại mới. public class JoergsAwesomeNewType {};Xem? Tôi chỉ phát minh ra một loại mới! Bạn cần mua CPU mới!
Jörg W Mittag

9
Không. Không. Trình biên dịch biết mã nào nó phải đặt trong nhị phân. Không có điểm nào trong việc giữ thông tin này xung quanh. Nếu bạn đang in một int, trình biên dịch sẽ đặt mã để in một int. Nếu bạn đang in một char, trình biên dịch sẽ đặt mã để in một char. Giai đoạn = Stage. Nhưng đó chỉ là một mô hình nhỏ. Mã để in char sẽ diễn giải mẫu bit theo một cách nhất định, mã để in int sẽ diễn giải bit theo một cách khác, nhưng không có cách nào để phân biệt mẫu bit là mẫu int với mẫu bit là một char, nó là một chuỗi các bit.
Jörg W Mittag

2
@ user16307: "Không phải tệp exe bao gồm thông tin về loại địa chỉ nào là loại dữ liệu?" Có lẽ. Nếu bạn biên dịch với dữ liệu gỡ lỗi, dữ liệu gỡ lỗi sẽ bao gồm thông tin về tên biến, địa chỉ và loại. Và đôi khi dữ liệu gỡ lỗi đó được lưu trữ trong tệp .exe (dưới dạng luồng nhị phân). Nhưng nó không phải là một phần của mã thực thi và nó không được sử dụng bởi chính ứng dụng, chỉ bởi một trình gỡ lỗi.
Ben Voigt

12

Máy tính không "biết" địa chỉ là gì, nhưng kiến ​​thức về những gì được đưa vào hướng dẫn của chương trình của bạn.

Khi bạn viết chương trình C ghi và đọc biến char, trình biên dịch sẽ tạo mã lắp ráp ghi đoạn dữ liệu đó ở đâu đó dưới dạng char và có một số mã khác ở đâu đó đọc địa chỉ bộ nhớ và diễn giải nó thành char. Điều duy nhất buộc hai thao tác này với nhau là vị trí của địa chỉ bộ nhớ đó.

Khi đến lúc đọc, các hướng dẫn không nói "xem loại dữ liệu nào ở đó", nó chỉ nói một cái gì đó như "tải bộ nhớ đó như một hình nổi". Nếu địa chỉ được đọc từ đã bị thay đổi hoặc một cái gì đó đã ghi đè lên bộ nhớ đó bằng một thứ gì đó không phải là float, CPU sẽ chỉ vui vẻ tải bộ nhớ đó dưới dạng float, và tất cả các loại vật lạ có thể xảy ra.

Thời gian tương tự xấu: hãy tưởng tượng một kho vận chuyển phức tạp, trong đó kho là bộ nhớ và mọi người chọn đồ là CPU. Một phần của kho 'chương trình' đặt các mặt hàng khác nhau trên kệ. Một chương trình khác đi và lấy các mặt hàng ra khỏi kho và đặt chúng vào hộp. Khi chúng được kéo ra, chúng không được kiểm tra, chúng chỉ đi vào thùng. Toàn bộ kho hoạt động bởi mọi thứ hoạt động đồng bộ, với các mặt hàng phù hợp luôn được đặt đúng chỗ vào đúng thời điểm, nếu không mọi thứ sẽ gặp sự cố, giống như trong một chương trình thực tế.


bạn sẽ giải thích thế nào nếu CPU tìm thấy 0x00000061 tại một thanh ghi và tìm nạp nó; và tưởng tượng chương trình console được cho là xuất ra ký tự này không phải là int. bạn có nghĩa là trong tệp exe đó có một số mã lệnh biết địa chỉ của 0x00000061 là một char và chuyển đổi thành một ký tự bằng cách sử dụng bảng ASCII?
dùng16307

7
Lưu ý rằng "mọi thứ gặp sự cố" thực sự là trường hợp tốt nhất. "Những điều kỳ lạ xảy ra" là kịch bản hay thứ hai, "những điều kỳ lạ xảy ra" thậm chí còn tồi tệ hơn, và trường hợp xấu nhất là "những điều xảy ra sau lưng bạn mà ai đó cố tình thao túng xảy ra theo cách họ muốn", aka một khai thác bảo mật.
Jörg W Mittag

@ user16307: Mã trong chương trình sẽ báo cho máy tính tìm nạp địa chỉ đó sau đó để hiển thị nó theo bất kỳ mã hóa nào đang được sử dụng. Cho dù dữ liệu trong vị trí bộ nhớ là ký tự ASCII hay rác hoàn chỉnh, máy tính không quan tâm. Một cái gì đó khác chịu trách nhiệm thiết lập địa chỉ bộ nhớ đó để có các giá trị mong đợi trong đó. Tôi nghĩ rằng nó có thể có lợi cho bạn để thử một số chương trình lắp ráp.
tên gì là

1
@ JörgWMittag: thực sự. Tôi đã nghĩ đến việc đề cập đến một lỗi tràn bộ đệm làm ví dụ nhưng quyết định nó sẽ chỉ khiến mọi thứ trở nên khó hiểu hơn.
tên gì là

@ user16307: Thứ hiển thị dữ liệu ra màn hình là một chương trình. Trên unixen truyền thống, đó là một thiết bị đầu cuối (một phần mềm mô phỏng thiết bị đầu cuối nối tiếp DEC VT100 - một thiết bị phần cứng có màn hình và bàn phím hiển thị bất cứ thứ gì đi vào modem của nó tới màn hình và gửi bất cứ thứ gì gõ trên bàn phím đến modem của nó). Trên DOS, nó là DOS (thực ra là chế độ văn bản của thẻ VGA của bạn nhưng hãy bỏ qua điều đó) và trên Windows là lệnh.com. Chương trình của bạn không biết rằng nó thực sự in ra các chuỗi, nó chỉ in ra một chuỗi byte (số).
slebetman

8

Nó không. Khi C được biên dịch thành mã máy, máy chỉ nhìn thấy một loạt các bit. Làm thế nào các bit đó được diễn giải phụ thuộc vào các hoạt động đang được thực hiện trên chúng trái ngược với một số siêu dữ liệu bổ sung.

Các loại bạn nhập trong mã nguồn của bạn chỉ dành cho trình biên dịch. Nó lấy loại mà bạn nói là dữ liệu được cho là và, với khả năng tốt nhất của nó, cố gắng đảm bảo rằng dữ liệu đó chỉ được sử dụng theo cách có ý nghĩa. Khi trình biên dịch đã thực hiện công việc tốt nhất có thể trong việc kiểm tra logic của mã nguồn của bạn, nó sẽ chuyển đổi nó thành mã máy và loại bỏ dữ liệu loại, vì mã máy không có cách nào thể hiện điều đó (ít nhất là trên hầu hết các máy) .


Điều tôi không hiểu là làm thế nào máy tính biết cho phép khi nó đọc giá trị của biến và địa chỉ như 10001 nếu là int hoặc char. Hãy tưởng tượng tôi nhấp vào một chương trình gọi là anyprog.exe. Ngay lập tức mã bắt đầu thực thi. Tập tin exe này có bao gồm thông tin về việc các biến được lưu trữ như trong hoặc char không? -
dùng16307

@ user16307 Không, không có thêm thông tin nào về việc một thứ gì đó là int hay char. Tôi sẽ thêm một số công cụ ví dụ sau, giả sử không có ai khác đánh bại tôi với nó.
8bittree

1
@ user16307: Tệp exe chứa thông tin đó một cách gián tiếp. Bộ xử lý thực thi chương trình không quan tâm đến các loại được sử dụng khi viết chương trình, nhưng phần lớn có thể được suy ra từ các hướng dẫn được sử dụng để truy cập các vị trí bộ nhớ khác nhau.
Bart van Ingen Schenau

@ user16307 thực sự có thêm một chút thông tin. Các tệp exe biết rằng một số nguyên có 4 byte nên khi bạn viết "int a", trình biên dịch sẽ thay đổi 4 byte cho một biến và do đó có thể tính địa chỉ của a và các biến khác sau.
Esben Skov Pedersen

1
@ user16307 không có sự khác biệt thực tế (bên cạnh kích thước của loại) sự khác biệt giữa int a = 65char b = 'A'một khi mã được biên dịch.

6

Hầu hết các bộ xử lý cung cấp các hướng dẫn khác nhau để làm việc với dữ liệu thuộc các loại khác nhau, vì vậy thông tin loại thường được "nướng" vào mã máy được tạo. Không cần lưu trữ siêu dữ liệu loại bổ sung.

Một số ví dụ cụ thể có thể giúp đỡ. Mã máy bên dưới được tạo bằng gcc 4.1.2 trên hệ thống x86_64 chạy SuSE Linux Enterprise Server (SLES) 10.

Giả sử mã nguồn sau:

int main( void )
{
  int x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

Đây là phần cốt lõi của mã lắp ráp được tạo tương ứng với nguồn trên (sử dụng gcc -S), với các nhận xét được thêm bởi tôi:

main:
.LFB2:
        pushq   %rbp               ;; save the current frame pointer value
.LCFI0:
        movq    %rsp, %rbp         ;; make the current stack pointer value the new frame pointer value
.LCFI1:                            
        movl    $1, -12(%rbp)      ;; x = 1
        movl    $2, -8(%rbp)       ;; y = 2
        movl    -8(%rbp), %eax     ;; copy the value of y to the eax register
        addl    -12(%rbp), %eax    ;; add the value of x to the eax register
        movl    %eax, -4(%rbp)     ;; copy the value in eax to z
        movl    $0, %eax           ;; eax gets the return value of the function
        leave                      ;; exit and restore the stack
        ret

Có một số nội dung bổ sung tiếp theo ret, nhưng nó không liên quan đến cuộc thảo luận.

%eaxlà một thanh ghi dữ liệu mục đích chung 32 bit. %rsplà một thanh ghi 64 bit được dành riêng để lưu con trỏ ngăn xếp , chứa địa chỉ của thứ cuối cùng được đẩy lên ngăn xếp. %rbplà một thanh ghi 64 bit dành riêng để lưu con trỏ khung , chứa địa chỉ của khung ngăn xếp hiện tại . Khung ngăn xếp được tạo trên ngăn xếp khi bạn nhập hàm và nó dành chỗ cho các đối số và biến cục bộ của hàm. Các đối số và biến được truy cập bằng cách sử dụng offset từ con trỏ khung. Trong trường hợp này, bộ nhớ cho biến xlà 12 byte "bên dưới" địa chỉ được lưu trữ %rbp.

Trong đoạn mã trên, chúng tôi sao chép giá trị số nguyên của x(1, được lưu trữ tại -12(%rbp)) vào thanh ghi %eaxbằng movlhướng dẫn, được sử dụng để sao chép các từ 32 bit từ vị trí này sang vị trí khác. Sau đó addl, chúng tôi gọi , trong đó thêm giá trị nguyên của y(được lưu trữ tại -8(%rbp)) vào giá trị đã có %eax. Chúng tôi sau đó lưu kết quả -4(%rbp), đó là z.

Bây giờ, hãy thay đổi điều đó để chúng ta xử lý doublecác giá trị thay vì các intgiá trị:

int main( void )
{
  double x, y, z;

  x = 1;
  y = 2;

  z = x + y;

  return 0;
}

Chạy gcc -Slại cho chúng ta:

main:
.LFB2:
        pushq   %rbp                              
.LCFI0:
        movq    %rsp, %rbp
.LCFI1:
        movabsq $4607182418800017408, %rax ;; copy literal 64-bit floating-point representation of 1.00 to rax
        movq    %rax, -24(%rbp)            ;; save rax to x
        movabsq $4611686018427387904, %rax ;; copy literal 64-bit floating-point representation of 2.00 to rax
        movq    %rax, -16(%rbp)            ;; save rax to y
        movsd   -24(%rbp), %xmm0           ;; copy value of x to xmm0 register
        addsd   -16(%rbp), %xmm0           ;; add value of y to xmm0 register
        movsd   %xmm0, -8(%rbp)            ;; save result to z
        movl    $0, %eax                   ;; eax gets return value of function
        leave                              ;; exit and restore the stack
        ret

Một số khác biệt. Thay vì movladdl, chúng tôi sử dụng movsdaddsd(gán và thêm phao có độ chính xác kép). Thay vì lưu trữ các giá trị tạm thời trong %eax, chúng tôi sử dụng %xmm0.

Ý tôi là khi tôi nói rằng loại này được "nướng" vào mã máy. Trình biên dịch chỉ cần tạo mã máy phù hợp để xử lý loại cụ thể đó.


4

Trong lịch sử , C coi bộ nhớ là bao gồm một số nhóm các vị trí được đánh số loạiunsigned char(còn được gọi là "byte", mặc dù không cần phải luôn luôn là 8 bit). Bất kỳ mã nào sử dụng bất cứ thứ gì được lưu trữ trong bộ nhớ đều cần biết khe hoặc thông tin nào được lưu trữ trong đó và biết những gì nên được thực hiện với thông tin ở đó [ví dụ: "diễn giải bốn byte bắt đầu tại địa chỉ 123: 456 là 32 bit giá trị dấu phẩy động "hoặc" lưu trữ 16 bit thấp hơn của số lượng được tính gần đây nhất thành hai byte bắt đầu từ địa chỉ 345: 678]. Bản thân bộ nhớ sẽ không biết và không quan tâm các giá trị được lưu trữ trong các khe nhớ "có nghĩa là gì". mã đã cố gắng ghi bộ nhớ bằng cách sử dụng một loại và đọc nó như một loại khác, các mẫu bit được lưu bởi ghi sẽ được diễn giải theo các quy tắc của loại thứ hai, với bất kỳ hậu quả nào có thể xảy ra.

Ví dụ: nếu mã được lưu trữ 0x12345678thành 32 bit unsigned intvà sau đó cố gắng đọc hai unsigned intgiá trị 16 bit liên tiếp từ địa chỉ của nó và một giá trị ở trên, thì tùy thuộc vào một nửa số unsigned intđược lưu trữ ở đâu, mã có thể đọc các giá trị 0x1234 và 0x5678 hoặc 0x5678 và 0x1234.

Tuy nhiên, Tiêu chuẩn C99 không còn yêu cầu bộ nhớ hoạt động như một loạt các khe được đánh số mà không biết gì về các mẫu bit của chúng đại diện . Trình biên dịch được phép hành xử như thể các khe cắm bộ nhớ nhận biết các loại dữ liệu được lưu trữ trong chúng và sẽ chỉ cho phép dữ liệu được ghi bằng bất kỳ loại nào khác ngoài unsigned charđược đọc bằng cách sử dụng loại unsigned charhoặc cùng loại như đã viết với; trình biên dịch tiếp tục được phép hành xử như thể các khe cắm bộ nhớ có sức mạnh và khuynh hướng tự ý làm hỏng hành vi của bất kỳ chương trình nào cố gắng truy cập bộ nhớ theo cách trái với các quy tắc đó.

Được:

unsigned int a = 0x12345678;
unsigned short p = (unsigned short *)&a;
printf("0x%04X",*p);

một số triển khai có thể in 0x1234 và một số triển khai khác có thể in 0x5678, nhưng theo Tiêu chuẩn C99, việc triển khai để in "QUY TẮC PHÁP!" hoặc làm bất cứ điều gì khác, theo lý thuyết rằng sẽ hợp pháp cho các vị trí bộ nhớ chứa aphần cứng ghi lại loại nào được sử dụng để ghi chúng và để phần cứng đó phản ứng với nỗ lực đọc không hợp lệ trong bất kỳ thời trang nào, kể cả bằng cách gây ra "QUY TẮC TƯƠI!" để được đầu ra.

Lưu ý rằng không có vấn đề gì nếu phần cứng như vậy thực sự tồn tại - thực tế là phần cứng như vậy có thể tồn tại một cách hợp pháp khiến cho trình biên dịch tạo ra mã hoạt động như thể nó chạy trên một hệ thống như vậy. Nếu trình biên dịch có thể xác định rằng một vị trí bộ nhớ cụ thể sẽ được viết dưới dạng một loại và đọc thành một loại khác, thì nó có thể giả vờ rằng nó đang chạy trên một hệ thống mà phần cứng có thể đưa ra quyết định như vậy và có thể phản hồi với bất kỳ mức độ thất bại nào mà tác giả trình biên dịch thấy phù hợp .

Mục đích của quy tắc này là cho phép các trình biên dịch biết rằng một nhóm byte giữ giá trị của một loại nào đó giữ một giá trị cụ thể tại một thời điểm và không có giá trị nào cùng loại được viết từ đó, để suy ra rằng nhóm đó byte sẽ vẫn giữ giá trị đó. Ví dụ, một bộ xử lý đã đọc một nhóm byte vào một thanh ghi, và sau đó muốn sử dụng lại thông tin đó trong khi nó vẫn còn trong thanh ghi, trình biên dịch có thể sử dụng nội dung thanh ghi mà không phải đọc lại giá trị từ bộ nhớ. Một tối ưu hóa hữu ích. Trong khoảng mười năm đầu tiên của quy tắc, vi phạm nó thường có nghĩa là nếu một biến được viết với một loại khác với loại được sử dụng để đọc nó, thì việc viết có thể hoặc không ảnh hưởng đến giá trị đọc. Hành vi như vậy trong một số trường hợp có thể là thảm họa, nhưng trong những trường hợp khác có thể vô hại,

Tuy nhiên, khoảng năm 2009, các tác giả của một số trình biên dịch như CLANG đã xác định rằng vì Tiêu chuẩn cho phép trình biên dịch làm bất cứ điều gì họ thích trong trường hợp bộ nhớ được viết bằng một loại và đọc như một loại khác, trình biên dịch sẽ suy ra rằng các chương trình sẽ không bao giờ nhận được đầu vào có thể gây ra một điều như vậy xảy ra Vì Standard cho biết trình biên dịch được phép làm bất cứ điều gì nó thích khi nhận được đầu vào không hợp lệ như vậy, nên mã này chỉ có tác dụng trong trường hợp Tiêu chuẩn áp đặt không có yêu cầu nào (và theo quan điểm của một số tác giả trình biên dịch,) nên được bỏ qua như không liên quan. Điều này thay đổi hành vi của các vi phạm răng cưa giống như bộ nhớ, được đưa ra yêu cầu đọc, có thể tùy ý trả về giá trị cuối cùng được viết bằng cùng loại với yêu cầu đọc hoặc bất kỳ giá trị nào gần đây được viết bằng một loại khác,


1
Đề cập đến hành vi không xác định khi gõ cắt tỉa cho người không hiểu làm thế nào không có RTTI dường như phản trực giác
Cole Johnson

@ColeJohnson: Thật tệ khi không có tên chính thức hoặc tiêu chuẩn cho phương ngữ C được hỗ trợ bởi 99% các trình biên dịch trước năm 2009, vì từ góc độ giảng dạy và ngôn ngữ thực tế, chúng nên được coi là các ngôn ngữ cơ bản khác nhau. Vì cùng một tên được đặt cho cả hai phương ngữ đã phát triển một số hành vi có thể dự đoán và tối ưu hóa trong hơn 35 năm, nên phương ngữ loại bỏ các hành vi đó cho mục đích tối ưu hóa, thật khó tránh khỏi nhầm lẫn khi nói về những thứ hoạt động khác nhau trong chúng. .
8/8/2015

Trong lịch sử C chạy trên các máy Lisp không cho phép chơi với các loại lỏng lẻo như vậy. Tôi khá chắc chắn rằng nhiều "hành vi có thể dự đoán và tối ưu hóa" được thấy 30 năm trước chỉ đơn giản là không hoạt động ở đâu ngoài BSD Unix trên VAX.
prosfilaes

@prosfilaes: Có lẽ "99% trình biên dịch được sử dụng từ 1999 đến 2009" sẽ chính xác hơn? Ngay cả khi trình biên dịch có các tùy chọn cho một số tối ưu hóa số nguyên khá tích cực, chúng vẫn chỉ là - các tùy chọn. Tôi không biết rằng tôi đã từng thấy một trình biên dịch trước năm 1999 không có chế độ không đảm bảo rằng int x,y,z;biểu thức được đưa ra x*y > zsẽ không bao giờ làm gì ngoài trả về 1 hoặc 0 hoặc khi vi phạm bí danh sẽ có bất kỳ ảnh hưởng nào ngoài việc để trình biên dịch tự ý trả về giá trị cũ hoặc mới.
supercat

1
... Trong đó các unsigned chargiá trị được sử dụng để xây dựng một loại "đến từ". Nếu một chương trình phân tách một con trỏ thành một unsigned char[], hiển thị ngắn gọn nội dung hex của nó trên màn hình, sau đó xóa con trỏ unsigned char[], và sau đó chấp nhận một số số hex từ bàn phím, sao chép chúng trở lại một con trỏ và sau đó hủy bỏ con trỏ đó , hành vi sẽ được xác định rõ trong trường hợp số được nhập vào khớp với số được hiển thị.
supercat

3

Trong C, nó không phải là. Các ngôn ngữ khác (ví dụ: Lisp, Python) có các loại động nhưng C được nhập tĩnh. Điều đó có nghĩa là chương trình của bạn phải biết loại dữ liệu để diễn giải đúng là ký tự, số nguyên, v.v.

Thông thường trình biên dịch sẽ giải quyết vấn đề này cho bạn và nếu bạn làm sai, bạn sẽ gặp lỗi thời gian biên dịch (hoặc cảnh báo).


Điều tôi không hiểu là làm thế nào máy tính biết cho phép khi nó đọc giá trị của biến và địa chỉ như 10001 nếu là int hoặc char. Hãy tưởng tượng tôi nhấp vào một chương trình gọi là anyprog.exe. Ngay lập tức mã bắt đầu thực thi. Tập tin exe này có bao gồm thông tin về việc các biến được lưu trữ như trong hoặc char không? -
dùng16307

1
@ user16307 Về cơ bản là không, tất cả thông tin đó đã bị mất hoàn toàn. Mã máy được thiết kế đủ tốt để thực hiện công việc của mình ngay cả khi không có thông tin đó. Tất cả các máy tính quan tâm là có tám bit liên tiếp tại địa chỉ 10001. Đó là công việc của bạn hoặc công việc của người biên dịch , tùy theo trường hợp, để theo kịp các công cụ như thế trong khi viết mã máy hoặc mã lắp ráp.
Panzercrisis

1
Lưu ý rằng gõ động không phải là lý do duy nhất để giữ lại các loại. Java được gõ tĩnh, nhưng nó vẫn phải giữ lại các kiểu, vì nó cho phép phản xạ động trên kiểu đó. Thêm vào đó, nó có tính đa hình thời gian chạy, tức là gửi phương thức dựa trên kiểu thời gian chạy, mà nó cũng cần kiểu. C ++ đặt mã gửi phương thức vào chính đối tượng (hay đúng hơn là lớp), vì vậy, nó không cần kiểu này theo một nghĩa nào đó (mặc dù dĩ nhiên vtable là một phần nào đó của kiểu, vì vậy, thực sự ít nhất là một phần của kiểu được giữ lại), nhưng trong Java, mã công văn phương thức được tập trung.
Jörg W Mittag

nhìn vào câu hỏi của tôi, tôi đã viết "khi một chương trình C thực thi?" Không phải chúng được lưu trữ gián tiếp trong tệp exe trong số các mã lệnh và cuối cùng chiếm vị trí trong bộ nhớ? Tôi viết lại điều này cho bạn một lần nữa: Nếu CPU tìm thấy 0x00000061 tại một thanh ghi và tìm nạp nó; và tưởng tượng chương trình giao diện điều khiển được cho là xuất ra ký tự này không phải là int. Có trong tệp exe đó (mã máy / mã nhị phân) một số mã lệnh biết địa chỉ của 0x00000061 là char và chuyển đổi thành ký tự bằng cách sử dụng bảng ASCII không? Nếu vậy nó có nghĩa là định danh char int gián tiếp trong nhị phân ???
dùng16307

Nếu giá trị là 0x61 và được khai báo là char (nghĩa là 'a') và bạn gọi một thói quen để hiển thị nó, cuối cùng sẽ có một cuộc gọi hệ thống để hiển thị ký tự đó. Nếu bạn đã khai báo nó như một int và gọi thủ tục hiển thị, trình biên dịch sẽ biết tạo mã để chuyển đổi 0x61 (thập phân 97) thành chuỗi ASCII 0x39, 0x37 ('9', '7'). Dòng dưới cùng: mã được tạo ra là khác nhau vì trình biên dịch biết để đối xử với chúng khác nhau.
Mike Harris

3

Bạn cần phải phân biệt giữa compiletimeruntimetrên một mặt và codedatamặt khác.

Từ góc độ máy móc, không có gì khác biệt giữa những gì bạn gọi codehoặc instructionsnhững gì bạn gọi data. Tất cả là do con số. Nhưng một số trình tự - những gì chúng ta sẽ gọi code- làm một cái gì đó chúng ta thấy hữu ích, những cái khác chỉ đơn giản crashlà máy.

Công việc được CPU thực hiện là một vòng lặp 4 bước đơn giản:

  • Lấy "dữ liệu" từ một địa chỉ nhất định
  • Giải mã hướng dẫn (nghĩa là "diễn giải" số là một instruction)
  • Đọc một địa chỉ hiệu quả
  • Thực hiện và lưu trữ kết quả

Đây được gọi là chu trình hướng dẫn .

Tôi đọc rằng A và 4 được lưu trữ trong các địa chỉ RAM ở đây. Nhưng còn a và x thì sao?

axlà các biến, là các trình giữ chỗ cho các địa chỉ, nơi chương trình có thể tìm thấy "nội dung" của các biến. Vì vậy, bất cứ khi nào biến ađược sử dụng, có hiệu quả địa chỉ của nội dung ađược sử dụng.

Khó hiểu nhất, làm thế nào để thực thi biết rằng a là char và x là int?

Việc thực hiện không biết gì cả. Từ những gì đã nói trong phần giới thiệu, CPU chỉ lấy dữ liệu và diễn giải dữ liệu này theo hướng dẫn.

Chức năng printf được thiết kế để "biết", loại đầu vào nào bạn đang đặt vào nó, tức là mã kết quả của nó đưa ra các hướng dẫn đúng cách xử lý một phân đoạn bộ nhớ đặc biệt. Tất nhiên, có thể điều chỉnh đầu ra vô nghĩa: sử dụng một địa chỉ, trong đó không có chuỗi nào được lưu trữ cùng với "% s" printf()sẽ dẫn đến đầu ra vô nghĩa chỉ bị dừng bởi một vị trí bộ nhớ ngẫu nhiên, trong đó có 0 ( \0).

Điều tương tự cũng xảy ra đối với điểm vào của một chương trình. Theo C64, có thể đưa các chương trình của bạn vào (gần) mọi địa chỉ đã biết. Các chương trình lắp ráp đã được bắt đầu với một lệnh được gọi systheo sau bởi một địa chỉ: sys 49152là nơi phổ biến để đặt mã trình biên dịch mã của bạn. Nhưng không có gì ngăn bạn tải ví dụ dữ liệu đồ họa vào 49152, dẫn đến sự cố máy sau khi "bắt đầu" từ thời điểm này. Trong trường hợp này, chu trình hướng dẫn bắt đầu bằng việc đọc "dữ liệu đồ họa" và cố gắng diễn giải nó thành "mã" (tất nhiên không có ý nghĩa gì); các hiệu ứng đôi khi đáng kinh ngạc;)

Giả sử một giá trị được lưu trữ ở đâu đó trong RAM là 10011001; Nếu tôi là chương trình thực thi mã, làm sao tôi biết liệu 10011001 này là char hay int?

Như đã nói: "bối cảnh" - tức là các hướng dẫn trước và tiếp theo - giúp xử lý dữ liệu theo cách chúng ta muốn. Từ góc độ máy, không có sự khác biệt trong bất kỳ vị trí bộ nhớ. intcharchỉ là từ vựng, có ý nghĩa trong compiletime; trong runtime(trên cấp độ lắp ráp), không có charhoặc int.

Điều tôi không hiểu là làm thế nào máy tính biết, khi nó đọc giá trị của một biến từ một địa chỉ như 10001, cho dù đó là int hay char.

Máy tính không biết gì. Các lập trình viên nào. Mã được biên dịch tạo ra bối cảnh , cần thiết để tạo ra kết quả có ý nghĩa cho con người.

Tập tin thực thi này có bao gồm thông tin về việc các biến được lưu trữ là kiểu int hay char không

Không . Các thông tin, cho dù đó là một inthoặc charmất. Nhưng mặt khác, bối cảnh (các hướng dẫn cho biết, cách xử lý các vị trí bộ nhớ, nơi lưu trữ dữ liệu) vẫn được giữ nguyên; nên implicitely có, "thông tin" là implicitely sẵn.


Phân biệt tốt đẹp giữa thời gian biên dịch và thời gian chạy.
Michael Blackburn

2

Chúng ta hãy giữ cuộc thảo luận này với ngôn ngữ C.

Chương trình bạn đang đề cập đến được viết bằng ngôn ngữ cấp cao như C. Máy tính chỉ hiểu ngôn ngữ máy. Các ngôn ngữ cấp cao hơn cung cấp cho người lập trình khả năng diễn đạt logic theo cách thân thiện hơn với con người, sau đó được dịch thành mã máy mà bộ vi xử lý có thể giải mã và thực thi. Bây giờ hãy để chúng tôi thảo luận về mã bạn đã đề cập:

char a = 'A';
int x = 4;

Hãy để chúng tôi cố gắng phân tích từng phần:

char / int được gọi là kiểu dữ liệu. Chúng báo cho trình biên dịch phân bổ bộ nhớ. Trong trường hợp của charnó sẽ là 1 byte và int2 byte. (Xin lưu ý kích thước bộ nhớ này một lần nữa phụ thuộc vào bộ vi xử lý).

a / x được gọi là định danh. Bây giờ đây là những cái tên "thân thiện với người dùng" được đặt cho các vị trí bộ nhớ trong RAM.

= báo cho trình biên dịch lưu trữ 'A' tại vị trí bộ nhớ avà 4 tại vị trí bộ nhớ x.

Vì vậy, định danh kiểu dữ liệu int / char chỉ được sử dụng bởi trình biên dịch chứ không phải bởi bộ vi xử lý trong khi thực hiện chương trình. Do đó chúng không được lưu trữ trong bộ nhớ.


Các định danh kiểu dữ liệu int / char không được lưu trữ trực tiếp trong bộ nhớ dưới dạng các biến, nhưng chúng không được lưu trữ gián tiếp trong tệp exe giữa các mã lệnh và cuối cùng có vị trí trong bộ nhớ? Tôi viết lại điều này cho bạn một lần nữa: Nếu CPU tìm thấy 0x00000061 tại một thanh ghi và tìm nạp nó; và tưởng tượng chương trình giao diện điều khiển được cho là xuất ra ký tự này không phải là int. Có trong tệp exe đó (mã máy / mã nhị phân) một số mã lệnh biết địa chỉ của 0x00000061 là char và chuyển đổi thành ký tự bằng cách sử dụng bảng ASCII không? Nếu vậy nó có nghĩa là định danh char int gián tiếp trong nhị phân ???
dùng16307

Không cho CPU tất cả các số của nó. Đối với ví dụ cụ thể của bạn, việc in trên bàn điều khiển không phụ thuộc vào việc biến là char hay int. Tôi sẽ cập nhật câu trả lời của mình với dòng chi tiết về cách chương trình cấp cao được chuyển đổi thành ngôn ngữ máy cho đến khi thực hiện chương trình.
prasad

2

Câu trả lời của tôi ở đây có phần đơn giản hóa và sẽ chỉ đề cập đến C.

Không, loại thông tin không được lưu trữ trong chương trình.

inthoặc charkhông phải là chỉ báo loại cho CPU; Chỉ đến trình biên dịch.

Exe được tạo bởi trình biên dịch sẽ có các hướng dẫn để thao tác ints nếu biến được khai báo là an int. Tương tự, nếu biến được khai báo là a char, exe sẽ chứa các hướng dẫn để thao tác a char.

Trong C:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

Chương trình này sẽ in thông điệp của nó, vì charintcùng giá trị trong RAM.

Bây giờ, nếu bạn đang tự hỏi làm thế nào printfquản lý để xuất ra 65cho một intAcho một char, đó là bởi vì bạn phải chỉ định trong "chuỗi định dạng" làm thế nào printfnên xử lý giá trị .
(Ví dụ: %ccó nghĩa là coi giá trị là a char%dcó nghĩa là coi giá trị là số nguyên; dù vậy, cùng một giá trị.)


2
Tôi đã hy vọng ai đó sẽ sử dụng một ví dụ sử dụng printf. @OP: int a = 65; printf("%c", a)sẽ xuất 'A'. Tại sao? Bởi vì bộ xử lý không quan tâm. Đối với nó, tất cả những gì nó thấy là bit. Chương trình của bạn đã nói với bộ xử lý lưu trữ 65 (trùng hợp giá trị của 'A'ASCII) tại avà sau đó xuất ra một ký tự, điều này rất vui. Tại sao? Bởi vì nó không quan tâm.
Cole Johnson

Nhưng tại sao một số người nói ở đây trong trường hợp C #, nó không phải là câu chuyện? Tôi đọc một số bình luận khác và họ nói trong C # và C ++, câu chuyện (thông tin về các loại dữ liệu) là khác nhau và thậm chí CPU không thực hiện tính toán. Bất cứ ý tưởng về điều đó?
dùng16307

@ user16307 Nếu CPU không thực hiện tính toán, chương trình sẽ không chạy. :) Đối với C #, tôi không biết, nhưng tôi nghĩ câu trả lời của tôi cũng áp dụng ở đó. Đối với C ++, tôi biết câu trả lời của tôi áp dụng ở đó.
BenjiWiebe

0

Ở mức thấp nhất, trong CPU vật lý thực tế không có loại nào cả (bỏ qua các đơn vị dấu phẩy động). Chỉ là các mẫu của bit. Một máy tính hoạt động bằng cách thao tác các mẫu bit, rất, rất nhanh.

Đó là tất cả những gì CPU từng làm, tất cả những gì nó có thể làm. Không có thứ gọi là int hay char.

x = 4 + 5

Sẽ thực thi như:

  1. Tải 00000100 vào đăng ký 1
  2. Tải 00000101 vào đăng ký 2
  3. IAdd đăng ký 1 để đăng ký 2, và lưu trữ trong đăng ký 1

Lệnh iadd kích hoạt phần cứng hoạt động như thể các thanh ghi 1 và 2 là các số nguyên. Nếu chúng không thực sự đại diện cho số nguyên, tất cả các loại điều có thể sai sau này. Kết quả tốt nhất thường là sụp đổ.

Trình biên dịch chọn hướng dẫn chính xác dựa trên các loại được cung cấp trong nguồn, nhưng trong mã máy thực tế được thực thi bởi CPU, không có loại nào, ở bất cứ đâu.

chỉnh sửa: Lưu ý rằng mã máy thực tế không thực sự đề cập đến 4, hoặc 5 hoặc số nguyên ở bất cứ đâu. nó chỉ là hai mẫu bit và một lệnh lấy hai mẫu bit, giả sử chúng là số nguyên và thêm chúng lại với nhau.


0

Câu trả lời ngắn, loại được mã hóa trong các lệnh CPU mà trình biên dịch tạo ra.

Mặc dù thông tin về loại hoặc kích thước của thông tin không được lưu trữ trực tiếp, trình biên dịch sẽ theo dõi thông tin này khi truy cập, sửa đổi và lưu trữ giá trị trong các biến này.

Làm thế nào để thực thi biết rằng a là một char và x là một int?

Nó không, nhưng khi trình biên dịch tạo mã máy thì nó biết. An intvà a charcó thể có kích cỡ khác nhau. Trong một kiến ​​trúc trong đó char có kích thước của byte và int là 4 byte, thì biến xkhông nằm trong địa chỉ 10001, mà còn ở 10002, 10003 và 10004. Khi mã cần tải giá trị của xvào thanh ghi CPU, nó sử dụng hướng dẫn để tải 4 byte. Khi tải một char, nó sử dụng lệnh để tải 1 byte.

Làm thế nào để chọn hướng dẫn nào trong hai hướng dẫn? Trình biên dịch quyết định trong quá trình biên dịch, nó không được thực hiện trong thời gian chạy sau khi kiểm tra các giá trị trong bộ nhớ.

Lưu ý rằng các thanh ghi có thể có kích cỡ khác nhau. Trên CPU Intel x86, EAX rộng 32 bit, một nửa là AX, 16 và AX được chia thành AH và AL, cả 8 bit.

Vì vậy, nếu bạn muốn tải một số nguyên (trên CPU x86), bạn sử dụng lệnh MOV cho số nguyên, để tải một char bạn sử dụng lệnh MOV cho ký tự. Cả hai đều được gọi là MOV, nhưng chúng có mã op khác nhau. Hiệu quả là hai hướng dẫn khác nhau. Loại biến được mã hóa trong hướng dẫn sử dụng.

Điều tương tự xảy ra với các hoạt động khác. Có nhiều hướng dẫn để thực hiện bổ sung, tùy thuộc vào kích thước của toán hạng và ngay cả khi chúng được ký hoặc không dấu. Xem https://en.wikipedia.org/wiki/ADD_(x86_in cản) liệt kê các bổ sung khác nhau có thể có.

Giả sử một giá trị được lưu trữ ở đâu đó trong RAM là 10011001; Nếu tôi là chương trình thực thi mã, làm sao tôi biết 10011001 này là char hay int

Đầu tiên, một char sẽ là 10011001, nhưng một int sẽ là 00000000 00000000 00000000 10011001, vì chúng có kích thước khác nhau (trên một máy tính có cùng kích thước như đã đề cập ở trên). Nhưng cho phép xem xét các trường hợp cho signed charvs unsigned char.

Những gì được lưu trữ trong một vị trí bộ nhớ có thể được diễn giải theo bất cứ cách nào bạn muốn. Một phần trách nhiệm của trình biên dịch C là đảm bảo rằng những gì được lưu trữ và đọc từ một biến được thực hiện một cách nhất quán. Vì vậy, không phải chương trình biết những gì được lưu trữ trong một vị trí bộ nhớ, mà là nó đồng ý trước khi nó sẽ luôn đọc và viết cùng một thứ ở đó. (không tính những thứ như kiểu đúc).


Nhưng tại sao một số người nói ở đây trong trường hợp C #, nó không phải là câu chuyện? Tôi đọc một số bình luận khác và họ nói trong C # và C ++, câu chuyện (thông tin về các loại dữ liệu) là khác nhau và thậm chí CPU không thực hiện tính toán. Bất cứ ý tưởng về điều đó?
dùng16307

0

Nhưng tại sao một số người nói ở đây trong trường hợp C #, nó không phải là câu chuyện? Tôi đọc một số bình luận khác và họ nói trong C # và C ++, câu chuyện (thông tin về các loại dữ liệu) là khác nhau và thậm chí CPU không thực hiện tính toán. Bất cứ ý tưởng về điều đó?

Trong các ngôn ngữ được kiểm tra kiểu như C #, việc kiểm tra kiểu được thực hiện bởi trình biên dịch. Mã benji đã viết:

int main()
{
    int a = 65;
    char b = 'A';
    if(a == b)
    {
        printf("Well, what do you know. A char can equal an int.\n");
    }
    return 0;
}

Đơn giản là sẽ từ chối biên dịch. Tương tự như vậy nếu bạn đã cố nhân một chuỗi và một số nguyên (tôi sẽ nói thêm, nhưng toán tử '+' bị quá tải với nối chuỗi và nó có thể chỉ hoạt động).

int a = 42;
string b = "Compilers are awesome.";
double[] c = a * b;

Trình biên dịch đơn giản sẽ từ chối tạo mã máy từ C # này, bất kể chuỗi của bạn có hôn đến mức nào.


-4

Các câu trả lời khác đều đúng trong đó về cơ bản mọi thiết bị tiêu dùng bạn sẽ gặp không lưu trữ thông tin loại. Tuy nhiên, đã có một số thiết kế phần cứng trong quá khứ (và ngày nay, trong bối cảnh nghiên cứu) sử dụng kiến trúc được gắn thẻ - chúng lưu trữ cả dữ liệu và loại (và có thể cả thông tin khác). Chúng nổi bật nhất bao gồm các máy Lisp .

Tôi mơ hồ nhớ lại việc nghe về một kiến ​​trúc phần cứng được thiết kế để lập trình hướng đối tượng có một cái gì đó tương tự, nhưng tôi không thể tìm thấy nó ngay bây giờ.


3
Câu hỏi nêu cụ thể nó đang đề cập đến ngôn ngữ C (không phải Lisp) và ngôn ngữ C không lưu trữ siêu dữ liệu biến. Mặc dù chắc chắn có thể thực hiện C để làm điều này, vì tiêu chuẩn không cấm nó, trong thực tế, điều đó không bao giờ xảy ra. Nếu bạn có những ví dụ liên quan đến câu hỏi, vui lòng cung cấp các trích dẫn cụ thể và cung cấp tài liệu tham khảo có liên quan đến ngôn ngữ C .

Chà, bạn có thể viết một trình biên dịch C cho máy Lisp, nhưng không ai sử dụng máy Lisp trong thời đại ngày nay nói chung. Nhân tiện, kiến ​​trúc hướng đối tượng là Rekursiv .
Nathan Ringo

2
Tôi nghĩ rằng câu trả lời này không hữu ích. Nó làm phức tạp mọi thứ vượt quá mức hiểu biết hiện tại về OP. Rõ ràng OP không hiểu mô hình thực thi cơ bản của CPU + RAM và cách trình biên dịch chuyển nguồn cấp cao tượng trưng thành nhị phân thực thi. Bộ nhớ được gắn thẻ, RTTI, Lisp, v.v., vượt xa những gì người hỏi cần biết theo ý kiến ​​của tôi và sẽ chỉ khiến anh ấy / cô ấy bối rối hơn.
Andres F.

Nhưng tại sao một số người nói ở đây trong trường hợp C #, nó không phải là câu chuyện? Tôi đọc một số bình luận khác và họ nói trong C # và C ++, câu chuyện (thông tin về các loại dữ liệu) là khác nhau và thậm chí CPU không thực hiện tính toán. Bất cứ ý tưởng về điều đó?
dùng16307
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.