[Powered by Google Translate] [CS50 Library] [Nate hardison] [Đại học Harvard] [Đây là CS50. CS50.TV] Thư viện CS50 là một công cụ hữu ích mà chúng tôi đã cài đặt trên thiết bị để làm cho nó dễ dàng hơn cho bạn để viết các chương trình nhắc nhở người sử dụng cho đầu vào. Trong video này, chúng tôi sẽ kéo rèm và nhìn vào những gì chính xác là trong thư viện CS50. Trong video vào thư viện C, chúng ta nói về cách bạn # bao gồm tiêu đề các tập tin của thư viện trong mã nguồn của bạn, và sau đó bạn liên kết với một tập tin thư viện nhị phân trong giai đoạn liên kết của quá trình biên dịch. Các tập tin tiêu đề chỉ định giao diện của thư viện. Nghĩa là, họ chi tiết tất cả các nguồn tài nguyên thư viện có sẵn để bạn sử dụng, như tờ khai chức năng, các hằng số, và các loại dữ liệu. Các tập tin thư viện nhị phân chứa thực hiện của thư viện, được biên dịch từ các tập tin tiêu đề của thư viện và thư viện c nguồn tập tin mã. Các tập tin thư viện nhị phân không phải là rất thú vị để xem xét vì nó, tốt, trong hệ nhị phân. Vì vậy, chúng ta hãy nhìn vào các tập tin tiêu đề cho thư viện thay vì. Trong trường hợp này, chỉ có một phần đầu tập tin được gọi là cs50.h. Chúng tôi đã cài đặt nó trong người sử dụng bao gồm thư mục cùng với các tập tin tiêu đề của hệ thống các thư viện khác. Một trong những điều đầu tiên bạn sẽ nhận thấy là cs50.h # bao gồm các tập tin tiêu đề từ các thư viện khác - float, giới hạn, tiêu chuẩn bool, và lib tiêu chuẩn. Một lần nữa, theo nguyên tắc không tái phát minh ra bánh xe, chúng tôi đã xây dựng thư viện CS0 bằng cách sử dụng các công cụ khác được cung cấp cho chúng tôi. Điều tiếp theo bạn sẽ thấy trong thư viện là chúng ta định nghĩa một kiểu mới được gọi là "chuỗi". Dòng này thực sự chỉ là tạo ra một bí danh cho các loại char *, do đó, nó không kỳ diệu niềm tự hào dân tộc các loại chuỗi mới với các thuộc tính thường liên kết với các đối tượng chuỗi trong các ngôn ngữ khác, chẳng hạn như chiều dài. Lý do chúng tôi đã làm điều này là để bảo vệ các lập trình mới từ các chi tiết đẫm máu của các con trỏ cho đến khi họ đã sẵn sàng. Các phần tiếp theo của tập tin header là khai báo các chức năng thư viện CS50 cung cấp cùng với các tài liệu. Chú ý mức độ chi tiết trong các ý kiến ​​ở đây. Điều này là siêu quan trọng để mọi người biết làm thế nào để sử dụng các chức năng này. Chúng tôi tuyên bố, lần lượt, chức năng nhắc nhở người sử dụng và các ký tự trở lại, tăng gấp đôi, phao, ints, dài chờ đợi, và chuỗi, bằng cách sử dụng loại dây riêng của chúng tôi. Theo nguyên tắc ẩn thông tin, chúng tôi đã đặt định nghĩa của chúng tôi trong một file riêng biệt thực hiện c - cs50.c - nằm trong thư mục nguồn của người dùng. Chúng tôi đã cung cấp tập tin để bạn có thể có một cái nhìn vào nó, học hỏi từ nó, và biên dịch lại nó trên các máy khác nhau nếu bạn muốn, mặc dù chúng ta nghĩ rằng nó tốt hơn để làm việc trên các thiết bị cho các lớp học này. Dù sao, chúng ta hãy có một cái nhìn vào nó bây giờ. Getchar, GetDouble, GetFloat, getInt, chức năng và GetLongLong tất cả đều được xây dựng trên đầu trang của các chức năng GetString. Nó chỉ ra rằng tất cả họ đều theo về cơ bản cùng một khuôn mẫu. Họ sử dụng một vòng lặp trong khi để nhắc nhở người dùng cho một dòng đầu vào. Họ trả về một giá trị đặc biệt nếu người sử dụng đầu vào một dòng trống. Họ cố gắng để phân tích đầu vào của người sử dụng như các loại thích hợp, có thể là, char một đôi, một phao, vv. Và sau đó họ có trả lại kết quả nếu đầu vào đã được phân tích thành công hoặc họ nhắc nhở lại cho người dùng. Ở mức độ cao, không có gì là thực sự khó khăn ở đây. Bạn có thể viết mã tương tự như cấu trúc mình trong quá khứ. Có lẽ một phần khó hiểu nhìn nhất là các cuộc gọi sscanf phân tích đầu vào của người dùng. Sscanf là một phần của gia đình chuyển đổi định dạng đầu vào. Nó sống trong tiêu chuẩn io.h, và công việc của mình để phân tích một chuỗi C, theo một định dạng cụ thể, lưu trữ các kết quả phân tích trong biến được cung cấp bởi người gọi. Kể từ khi chức năng chuyển đổi định dạng đầu vào là rất hữu ích, chức năng được sử dụng rộng rãi không phải là siêu trực quan ban đầu, chúng ta sẽ đi qua sscanf hoạt động như thế nào. Đối số đầu tiên để sscanf là một char * - một con trỏ đến một nhân vật. Đối với chức năng hoạt động đúng, rằng nhân vật phải là ký tự đầu tiên của một chuỗi C, chấm dứt với các ký tự null 0 \. Đây là một chuỗi để phân tích Đối số thứ hai để sscanf là một chuỗi định dạng, thường thông qua như là một hằng chuỗi, và bạn có thể đã thấy một chuỗi như thế này trước khi sử dụng printf. Một dấu hiệu% trong chuỗi định dạng cho thấy một xác định chuyển đổi. Các nhân vật ngay lập tức sau một dấu phần trăm, cho biết loại C mà chúng tôi muốn sscanf để chuyển đổi. GetInt, bạn thấy rằng có d% c%. Điều này có nghĩa là sscanf sẽ cố gắng để một int thập phân - d% và char c%. Đối với mỗi xác định chuyển đổi các chuỗi định dạng, sscanf hy vọng một đối số tương ứng sau đó trong danh sách đối số của nó. Lập luận phải trỏ đến một vị trí thích hợp đánh máy trong đó để lưu trữ các kết quả của việc chuyển đổi. Cách điển hình của việc này là để tạo ra một biến trên stack trước khi cuộc gọi sscanf cho mỗi mục mà bạn muốn phân tích từ chuỗi và sau đó sử dụng các nhà điều hành địa chỉ ký hiệu "- vượt qua con trỏ những biến cuộc gọi sscanf. Bạn có thể thấy rằng trong getInt chúng tôi làm chính xác này. Ngay trước khi cuộc gọi sscanf, chúng ta khai báo một int được gọi là n và gọi một c char trên stack, và chúng tôi vượt qua con trỏ cho họ vào các cuộc gọi sscanf. Đưa các biến trên stack được ưa thích hơn bằng cách sử dụng không gian phân bổ trên heap với malloc, kể từ khi bạn tránh những phí của các cuộc gọi malloc, và bạn không phải lo lắng về việc rò rỉ bộ nhớ. Nhân vật không bắt đầu bằng một dấu phần trăm không nhắc chuyển đổi. Thay vào đó họ chỉ cần thêm vào các đặc điểm kỹ thuật định dạng. Ví dụ, nếu các chuỗi định dạng trong getInt là một d% thay vào đó, sscanf sẽ tìm kiếm một lá thư tiếp theo là một int, và trong khi nó sẽ cố gắng để chuyển đổi các int, nó sẽ không làm bất cứ điều gì khác với một. Ngoại lệ duy nhất là khoảng trắng. Ký tự khoảng trắng trong chuỗi định dạng phù hợp với bất kỳ số lượng khoảng trắng - thậm chí không có gì cả. Vì vậy, đó là lý do tại sao các bình luận đề cập đến có thể có hàng đầu thế giới và / hoặc dấu khoảng trắng. Vì vậy, vào thời điểm này có vẻ như cuộc gọi sscanf của chúng tôi sẽ cố gắng phân tích chuỗi đầu vào của người dùng bằng cách kiểm tra để có thể hàng đầu khoảng trắng, tiếp theo là một int sẽ được chuyển đổi và lưu trữ trong các biến int n tiếp theo là một số lượng khoảng trắng, và tiếp theo là một nhân vật lưu trữ trong biến char c. Điều gì về giá trị trả lại? Sscanf sẽ phân tích các dòng đầu vào từ đầu đến cuối, dừng lại khi nó đạt đến kết thúc hoặc khi một nhân vật trong đầu vào không phù hợp với một ký tự định dạng hoặc khi nó không thể làm cho một chuyển đổi. Giá trị trả về của nó được sử dụng duy nhất khi nó dừng lại. Nếu nó dừng lại, bởi vì nó đạt đến kết thúc của chuỗi đầu vào trước khi thực hiện bất kỳ chuyển đổi và trước khi không phù hợp với một phần của chuỗi định dạng, sau đó kết thúc tập tin đặc biệt liên tục được trả về. Nếu không, nó trả về số chuyển đổi thành công, mà có thể là 0, 1, hoặc 2, kể từ khi chúng tôi đã yêu cầu hai chuyển đổi. Trong trường hợp của chúng tôi, chúng tôi muốn chắc chắn rằng người dùng gõ vào một int và chỉ có một int. Vì vậy, chúng tôi muốn sscanf để trở về 1. Xem tại sao? Nếu sscanf trở về 0, sau đó không có chuyển đổi đã được thực hiện, do đó, người dùng gõ một cái gì đó khác hơn nhiều so với một int tại đầu của đầu vào. Nếu sscanf trả về 2, sau đó người dùng không đúng cách gõ nó tại đầu của đầu vào, nhưng sau đó gõ một số ký tự không phải khoảng trắng sau đó kể từ khi% c chuyển đổi thành công. Wow, đó là một lời giải thích khá dài cho một cuộc gọi chức năng. Dù sao, nếu bạn muốn biết thêm thông tin trên sscanf, anh, chị, em ruột của nó, kiểm tra các trang người đàn ông, Google, hoặc cả hai. Có rất nhiều lựa chọn chuỗi định dạng, và chúng có thể giúp bạn tiết kiệm rất nhiều lao động thủ công khi cố gắng phân tích chuỗi trong C. Chức năng cuối cùng trong thư viện để xem xét là GetString. Nó chỉ ra rằng GetString là một chức năng khó khăn để viết đúng, mặc dù nó có vẻ như một nhiệm vụ đơn giản, phổ biến,. Tại sao điều này là trường hợp? Vâng, chúng ta hãy suy nghĩ về việc làm thế nào chúng ta sẽ để lưu trữ các dòng mà người dùng gõ vào. Kể từ khi một chuỗi là một chuỗi các ký tự, chúng tôi có thể muốn để lưu trữ nó trong một mảng trên stack, nhưng chúng tôi sẽ cần phải biết bao lâu các mảng là có được khi chúng ta khai báo nó. Tương tự như vậy, nếu chúng ta muốn đặt nó trên heap, chúng ta cần phải vượt qua để malloc số byte chúng ta muốn dự trữ, nhưng điều này là không thể. Chúng tôi không có ý tưởng bao nhiêu ký tự người dùng sẽ gõ vào trước khi người dùng thực sự không gõ. Một giải pháp ngây thơ cho vấn đề này là chỉ cần dự trữ một đoạn lớn của không gian, nói, một khối 1000 ký tự cho đầu vào của người dùng, giả định rằng người sử dụng không bao giờ sẽ nhập vào một chuỗi dài. Đây là một ý tưởng tồi vì hai lý do. Đầu tiên, giả định rằng người dùng thường không gõ trong chuỗi dài, bạn có thể lãng phí rất nhiều bộ nhớ. Trên máy móc hiện đại, điều này có thể không là một vấn đề nếu bạn làm điều này trong một hoặc hai trường hợp cá biệt, nhưng nếu bạn đang dùng đầu vào người sử dụng trong một vòng lặp và lưu trữ để sử dụng sau, bạn có thể nhanh chóng hút một tấn của bộ nhớ. Ngoài ra, nếu chương trình bạn đang viết cho một máy tính nhỏ hơn - một thiết bị giống như một điện thoại thông minh hoặc một cái gì đó khác với bộ nhớ hạn chế - giải pháp này sẽ gây ra vấn đề nhanh hơn rất nhiều. , Lý do thứ hai nghiêm trọng hơn để không làm điều này là nó rời khỏi chương trình của bạn dễ bị tổn thương những gì được gọi là một tấn công tràn bộ đệm. Trong lập trình, một bộ đệm là bộ nhớ được sử dụng để lưu trữ tạm thời dữ liệu đầu vào hoặc đầu ra, mà trong trường hợp này là khối 1000-char của chúng tôi. Một lỗi tràn bộ đệm xảy ra khi dữ liệu được ghi quá khứ kết thúc của khối. Ví dụ, nếu một người sử dụng thực hiện loại trong hơn 1000 ký tự. Bạn có thể đã có kinh nghiệm này vô tình khi lập trình với mảng. Nếu bạn có một mảng của 10 ints, không có gì ngăn bạn cố gắng để đọc hoặc viết int 15. Không có cảnh báo lỗi trình biên dịch hoặc. Chương trình chỉ sai lầm ngớ ngẩn thẳng về phía trước và truy cập bộ nhớ nơi nó nghĩ rằng int 15 sẽ được, và điều này có thể ghi đè lên các biến số khác của bạn. Trong trường hợp xấu nhất, bạn có thể ghi đè lên một số chương trình của bạn nội bộ cơ chế kiểm soát, gây ra chương trình của bạn thực sự thực hiện các hướng dẫn khác nhau hơn bạn dự định. Bây giờ, nó không phải là phổ biến để làm điều này vô tình, nhưng đây là một kỹ thuật khá phổ biến mà kẻ xấu sử dụng để phá vỡ chương trình và đặt mã độc hại trên máy tính của người khác. Vì vậy, chúng ta có thể không chỉ sử dụng giải pháp ngây thơ của chúng tôi. Chúng ta cần một cách để ngăn chặn các chương trình của chúng tôi là dễ bị tổn thương một cuộc tấn công tràn bộ đệm. Để làm điều này, chúng ta cần đảm bảo rằng bộ đệm của chúng tôi có thể phát triển như chúng ta đọc đầu vào từ người sử dụng. Các giải pháp? Chúng tôi sử dụng một bộ đệm được phân bổ đống. Từ khi chúng tôi có thể thay đổi kích thước nó bằng cách sử dụng các chức năng thay đổi kích cỡ realloc, và chúng tôi theo dõi của hai số - chỉ số của khe trống bên cạnh trong bộ đệm và chiều dài hoặc năng lực của bộ đệm. Chúng ta đọc trong các ký tự từ một người sử dụng tại một thời gian bằng cách sử dụng chức năng fgetc. Đối số chức năng fgetc mất - stdin - là một tham chiếu đến chuỗi đầu vào tiêu chuẩn, mà là một kênh đầu vào preconnected được sử dụng để chuyển đầu vào của người sử dụng từ các thiết bị đầu cuối cho chương trình. Bất cứ khi nào người sử dụng các loại trong một nhân vật mới, chúng tôi kiểm tra xem nếu các chỉ số của khe miễn phí tiếp theo cộng với 1 là lớn hơn dung lượng của bộ đệm. +1 Những có bởi vì nếu chỉ số miễn phí tiếp theo là 5, sau đó chiều dài của bộ đệm của chúng tôi phải là 6 đến 0 lập chỉ mục. Nếu chúng tôi đã chạy ra khỏi không gian trong bộ đệm, sau đó chúng tôi cố gắng để thay đổi kích cỡ của nó, tăng gấp đôi nó để chúng ta cắt giảm số lần mà chúng ta thay đổi kích thước nếu người dùng đang gõ vào một chuỗi thực sự dài. Nếu chuỗi đã nhận được quá lâu hoặc nếu chúng ta chạy ra khỏi bộ nhớ heap, chúng tôi miễn phí bộ đệm của chúng tôi và vô giá trị trở lại. Cuối cùng, chúng ta nối char vào bộ đệm. Một khi các số truy cập của người dùng vào hoặc trở lại, báo hiệu một dòng mới, hoặc char đặc biệt kiểm soát d - dấu sự kết thúc của đầu vào, chúng tôi làm một kiểm tra để xem nếu người sử dụng thực sự gõ vào bất cứ điều gì ở tất cả. Nếu không, chúng tôi trở về null. Nếu không, bởi vì bộ đệm của chúng tôi có lẽ lớn hơn chúng ta cần, trong trường hợp xấu nhất, nó gần như hai lần lớn như chúng ta cần kể từ khi chúng tôi tăng gấp đôi mỗi khi chúng ta thay đổi kích thước, chúng tôi thực hiện một bản sao mới của chuỗi bằng cách sử dụng chỉ là số tiền của không gian mà chúng ta cần. Chúng tôi thêm một thêm 1 cuộc gọi malloc, để có không gian cho các ký tự đặc biệt terminator vô giá trị - \ 0, mà chúng ta nối chuỗi một khi chúng ta sao chép trong phần còn lại của các nhân vật, sử dụng strncpy thay vì strcpy để chúng tôi có thể xác định chính xác có bao nhiêu ký tự chúng ta muốn sao chép. Strcpy sao chép cho đến khi nó cập một \ 0. Sau đó, chúng tôi miễn phí bộ đệm của chúng tôi và trả lại các bản sao để người gọi. Ai biết như một chức năng đơn giản, dường như có thể được như vậy phức tạp? Bây giờ bạn biết những gì đi vào thư viện CS50. Tên tôi là Nate hardison, và đây là CS50. [CS50.TV]