[Powered by Google Translate] [סעיף 5 - יותר נוח] [רוב אודן - אוניברסיטת הרווארד] [זה CS50. - CS50.TV] כמו שאמרתי בדוא"ל שלי, יש הרבה דברים שאתה יכול להשתמש מלבד המכשיר בעצם לעשות את הערכות הבעייתיות. אנו ממליצים לך לעשות את זה במכשיר רק בגלל שאז אנחנו יכולים בקלות רבה יותר לסייע לך ואנחנו יודעים איך הכל הולכים לעבודה. אבל כדוגמה אחת שבו אתה יכול לעשות דברים אם, למשל, אין לך גישה למכשיר או שאתה רוצה לעבוד במרתף מרכז המדע - שבעצם יש להם את המכשיר מדי - אם אתה רוצה לעבוד בכל מקום. דוגמה אחת היא האם ראה / שמעת על SSH? SSH הוא בעצם בדיוק כמו להתחבר למשהו. למעשה, כרגע אני SSHed לתוך המכשיר. אני לא עובד ישירות במכשיר. הנה המכשיר, ואם אתה מסתכל פה אתה רואה את כתובת ה-IP. אני לא אעבוד במכשיר עצמו; אני תמיד בא אל חלון / מסוף חלון iTerm2. אתה יכול SSH שלכתובת ה-IP, jharvard@192.168.129.128 ssh. אני זוכר את המספר הזה בקלות רבה בגלל שזה דפוס כזה נחמד. אבל שתשאל אותי לסיסמה שלי, ועכשיו אני במכשיר. בעיקרון, בשלב זה, אם נפתח הטרמינל הפנימי של המכשיר עצמו, ממשק זה, אבל אתה משתמש בו, הוא בדיוק אותו הדבר כממשק אני משתמש כאן, אבל עכשיו אתה SSHed. אתה לא צריך SSH למכשיר. דוגמה אחת למקום אחר אתה יכול SSH להיא שאני די בטוח שיש לך כברירת מחדל - אה. גדול יותר. כולכם צריכים להיות על ידי חשבונות FAS ברירת המחדל בשרתי FAS. עבורי, הייתי SSH לrbowden@nice.fas.harvard.edu. זה הולך לשאול אותך, כי בפעם הראשונה, ואתה אומר כן. הסיסמה שלי היא רק הולכת להיות הסיסמה של FAS שלי. ועכשיו, אני SSHed לשרתים הנחמדים, ואני יכול לעשות מה שאני רוצה כאן. הרבה שיעורים אתה עלול לקחת, כמו 124, הולכים להיות לך להעלות את דברים לכאן למעשה להגיש סטי הבעיה שלך. אבל אומר שאין לך גישה למכשיר שלך. ואז אתה יכול לעשות דברים, כמו כאן זה אומר - זה רק בחלק שלנו של שאלות. זה יבקש ממך לעשות את זה במכשיר. במקום זה אני פשוט אעשה את זה בשרת. אני הולך לפתוח את זה. הבעיה הולכת להיות שאתה רגיל להשתמש במשהו כמו gedit או מה שבתוך המכשיר. אתה לא הולך לקחת את זה על שרת FAS. זה הכל רק הולך להיות ממשק טקסטואלי זה. אז אתה יכול גם אחד, לנסות ללמוד עורך טקסט שיש להם. יש להם ננו. ננו הוא בדרך כלל די קל לשימוש. אתה יכול להשתמש בחצים שלך והקלד כרגיל. אז זה לא קשה. אם אתה רוצה להגיע ממש מפואר שתוכל להשתמש Emacs, שאני כנראה לא היה צריך לפתוח, כי אני אפילו לא יודע איך לסגור את Emacs. הבקרה X, C בקרה? כן. או שאתה יכול להשתמש Vim, וזה מה שאני משתמש. ולכן אלה הם האפשרויות שלך. אם אתה לא רוצה לעשות את זה, אתה גם יכול, אם אתה מסתכל על manual.cs50.net-- אה. במחשב, אתה יכול SSH באמצעות שפכטל, שאתה הולך צריך להוריד בנפרד. במקינטוש, אתה יכול פשוט על ידי שימוש במסוף ברירת מחדל, או שאתה יכול להוריד iTerm2, שזה כמו מסוף יפה, מפואר. אם אתה הולך לmanual.cs50.net תראה קישור לNotepad + +, וזה מה שאתה יכול להשתמש במחשב אישי. זה מאפשר לך SFTP מNotepad + +, שהוא בעצם SSH. מה זה ייתן לך לעשות הוא לערוך את הקבצים שלך באופן מקומי, ואז כל פעם שאתה רוצה לשמור אותם, זה יחסוך לnice.fas, איפה אתה יכול גם להפעיל אותם. והמקביל במקינטוש הולך להיות TextWrangler. אז זה מאפשר לך לעשות את אותו דבר. זה מאפשר לך לערוך קבצים באופן מקומי ולשמור אותם לnice.fas, איפה אתה יכול גם להפעיל אותם. אז אם אתה אי פעם נתקע בלי מכשיר, יש לך אפשרויות אלה עדיין לעשות סטי הבעיה שלך. הבעיה אחת הוא הולך להיות שאתה לא הולך לספרייה יש CS50 בגלל nice.fas לא כברירת מחדל יש את זה. אתה יכול גם להוריד את ספריית CS50 - אני לא חושב שאני צריך את זה בשלב זה. אתה יכול גם להוריד את ספריית CS50 ולהעתיק אותו לnice.fas, או שאני חושב בשלב זה אנחנו לא משתמשים בזה יותר בכל מקרה. ואם כן, אתה יכול לעת עתה להחליף אותו היישומים של הפונקציות בCS50 ספרייה בכל מקרה. כך שלא צריך להיות שהרבה מהגבלה. וזהו זה. אני אחזור למכשיר עכשיו, יצטרך לעשות הכל במכשיר. כאשר מסתכלים על הקטע של שאלות שלנו, בהתחלה, כמו שאמרתי בדוא"ל שלי, יש לנו על מה לדבר אחד קצר שהייתם אמור לצפות. יש לנו את הפניית & צינורות ועל שלוש שאלות הבאות. לאיזה זרם אל פונקציות כמו printf לכתוב כברירת מחדל? אז זרם. מהו זרם? זרם הוא בעצם כמו שזה רק חלק - זה אפילו לא מקור 1s ו 0s. הזרם זה כמו לבקש כאן הוא פלט סטנדרטי. ויצאתי כל כך סטנדרטי הוא זרם שכשאתה כותב אליו, הוא מופיע על המסך. את התקן, על ידי זרם, זה אומר שאתה פשוט כותב 1s ו 0s אליו, והקצה השני של את התקן פשוט קורא שמהנחל. זה רק מחרוזת של 1s ו 0s. אתה יכול לכתוב לזרמים או שאתה יכול לקרוא מזרמים תלוי מה הוא למעשה זרם. שני הזרמים האחרים כברירת המחדל הם סטנדרטיים ובשגיאה סטנדרטית. תקן הוא בכל פעם שאתה GetString, זה מחכה לך לחומר הזנה. אז זה מחכה לך, זה ממש מחכה בסטנדרטי ב, וזה באמת מה שאתה מקבל בעת ההקלדה במקלדת. אתה מקליד לתוך סטנדרטי פנימה שגיאה סטנדרטית היא בעצם שווה ערך לפלט סטנדרטי, אבל זה שהתמחה בעת הדפסה לשגיאה סטנדרטית, אתה אמור להדפיס הודעות שגיאה היחידה ש כדי שתוכל להבחין בין ההודעות הרגילות המודפסות למסך לעומת הודעות שגיאה תלויות אם הם הלכו לפלט סטנדרטי או שגיאה סטנדרטית. קבצים גם כן. יצאו רגיל, סטנדרטי ובטעות התקן הם פשוט זרמים מיוחדים, אבל באמת כל קובץ, בעת פתיחת קובץ, הוא הופך לזרם של בתים שבו אתה יכול פשוט לקרוא שמהנחל. אתה, על פי רוב, ניתן רק לחשוב על קובץ כזרם של בתים. אז זרמים מה שהם כותבים לברירת מחדל? פלט סטנדרטי. מה ההבדל בין> ו? >> האם מישהו לראות את הווידאו מראש? אוקיי. > הולך להיות איך אתה להפנות לקבצים, ו>> גם הולך לניתוב פלט לקבצים, אבל זה קורה במקום לצרף לקובץ. לדוגמה, תניח שיש בי dict ממש כאן, ורק דברים הפנימיים של dict הוא חתול, חתול, כלב, דגים, כלב. פקודה אחת שיש לך בשורת הפקודה היא חתול, שרק הולך להדפיס מה שיש בקובץ. לכן, כאשר אני אומר dict חתול, זה הולך להדפיס חתול, חתול, כלב, דגים, כלב. זה כל מה שהחתול עושה. זה אומר שזה מודפס לסטנדרטי את החתול, חתול, כלב, דגים, כלב. אם אני במקום שרצונך לנתב מחדש לקובץ, אני יכול להשתמש> ולהפנות אותו לכל הקובץ. אני אתקשר אל קובץ הקובץ. אז עכשיו אם אני ls, אני אראה שיש לי קובץ חדש בשם קובץ. ואם אני פותח אותו, זה הולך להיות בדיוק מה שהחתול לשים בשורת הפקודה. אז עכשיו אם אני עושה את זה שוב, ואז זה הולך להפנות את הפלט לקובץ, ואני הולך לקבל את אותו הדבר בדיוק. אז מבחינה טכנית, זה בטל לגמרי את מה שהיה לנו. ונראים אם אני משנה dict, הוצאתי את הכלב. עכשיו אם dict לתוך קובץ שהחתול שוב, אנחנו הולכים לתת את הגרסה החדשה עם הכלב הוסר. אז זה עוקף אותה לחלוטין. במקום זאת, אם אנו משתמשים >>, זה הולך לצרף קובץ. כעת, פתיחת קובץ, שאנו רואים שיש לנו בדיוק אותו הדבר נדפס פעמים כי זה היה שם פעם אחת, ולאחר מכן צרפנו אל המקור. אז זה מה ש> ו>> לעשות. האם הפעם הבאה תשאלנה - הוא אינו שואל על זה. האחד האחר שיש לנו הוא <, שאם> מפנה את התקן, <הולך להיות סטנדרטי להפנות פנימה בואו נראים אם יש לנו דוגמה. אני יכול לכתוב אחד מהיר אמיתי. בואו ניקח כל קובץ, hello.c. קובץ פשוט יחסית. אני מקבל רק מחרוזת ולאחר מכן להדפיס "שלום" כל מה שאני רק נכנסתי למחרוזת הייתה. אז לעשות שלום ואחרי. / שלום. עכשיו זה מעודד אותי להיכנס למשהו, מה שאומר שהיא מחכה על דברים שנכנסו פנימה סטנדרטי אז מה שאני רוצה להיכנס לסטנדרטי פנימה אנחנו פשוט הולכים להגיד שלום, רוב! אז זה מדפיס את התקן שלום, רוב! אם אני עושה. / שלום ולאחר מכן כוון מחדש, לעת עתה אתה יכול להפנות רק מקובץ. אז אם אני שם בקובץ כלשהו, ​​txt, ושמתי את רוב, אם נגמר לי שלום ואז להפנות לקובץ txt. / הלו, זה הולך לומר שלום, רוב! באופן מיידי. כשזה יגיע ראשון GetString והיא ממתינה בתקן ב, הסטנדרטי בהוא כבר לא מחכה במקלדת לנתונים כדי לקבל הזנה. במקום זאת, יש לנו מנותב הסטנדרטי בלקרוא מקובץ txt. וכך זה הולך לקרוא מקובץ txt, שהוא רק הקו רוב, ואז זה הולך להדפיס שלום, רוב! ואם אני רוצה, אני יכול גם לעשות. / שלום אתה עושה 2>, זה הפניית שגיאה סטנדרטית. אז אם משהו הלך לשגיאה סטנדרטית, זה לא ילך הכניס לתוך txt2. אבל שם לב אם אני עושה 2>, אז זה עדיין מדפיס שלום, רוב! לשורת הפקודה כי אני רק הפניית שגיאה סטנדרטית, אני לא הפניית פלט סטנדרטי. שגיאה סטנדרטית ופלט סטנדרטי הם שונים. אם אתה רוצה באמת לכתוב לשגיאה סטנדרטית, אז אני יכול לשנות את זה כדי להיות fprintf לstderr. אז printf, כברירת מחדל, מדפיס לפלט סטנדרטי. אם אני רוצה להדפיס לשגיאה סטנדרטית באופן ידני, ואז אני צריך להשתמש fprintf וציין מה שאני רוצה להדפיס. אם במקום שעשיתי stdout fprintf, אז זה בעצם שווה ערך לprintf. אבל fprintf לשגיאה סטנדרטית. אז עכשיו, אם אני להפנות את זה לtxt2, שלום, רוב! עדיין הוא מקבל מודפס בשורת הפקודה מפני שנעשה מודפס לשגיאה סטנדרטית ואני רק הפניית פלט סטנדרטי. אם אני עכשיו להפנות שגיאה סטנדרטית, עכשיו זה לא יודפס, וtxt2 הולך להיות שלום, רוב! אז עכשיו, אתה יכול להדפיס טעויות שלך בפועל לשגיאה סטנדרטית ולהדפיס את ההודעות הרגילות שלך לפלט סטנדרטי. ולכן כאשר אתה מפעיל את התכנית שלך, אתה יכול להפעיל אותו באותה מידה. / שלום מסוג זה עם 2> כך שהתכנית שלך הולכת לרוץ באופן רגיל, אבל כל הודעות שגיאה שאתה מקבל אתה יכול לבדוק מאוחר יותר ביומן השגיאות שלך, כך טעויות, ואז לחפש הקובץ מאוחר יותר והטעויות שלך יהיה כל שגיאות שקרו. שאלות? האחרון הוא צינור, שאתה יכול לחשוב עליו כהוצאה סטנדרטית מפקודה אחת והופך אותו לסטנדרט בשל הפקודה הבאה. דוגמה כאן היא הד היא דבר שורת פקודה שרק הולך מהדהדים מה שאני אשים כטענתה. אני לא היה מכניס את הציטוטים. אקו בלה, בלה, בלה הוא פשוט הולך להדפיס בלה, בלה, בלה. קודם, כשאמרתי שאני צריך לשים את רוב לקובץ txt כי אני יכול להפנות את קבצי txt בלבד, במקום, / אם אני חוזר על רוב ולאחר מכן אותו לתוך צינור. / שלום, שגם לעשות את אותו הסוג של דבר. זה לוקח את הפלט של פקודה זו, הד רוב, ומשתמש בו כקלט ל. / שלום. אתה יכול לחשוב על זה כלהפנות 1 הד רוב לקובץ ואז קלט לתוך קובץ. / שלום שזה היה סתם בoutputted. אבל זה לוקח את הקובץ הזמני מחוץ לתמונה. שאלות על זה? השאלה הבאה הולכת לערב זה. מה אתה יכול להשתמש בצינור כדי למצוא את מספר השמות ייחודיים בקובץ שנקרא names.txt? הפקודות שאנחנו הולכים רוצים להשתמש כאן הן ייחודיים, ולכן uniq, ולאחר מכן ב"ש. אתה יכול לעשות uniq אדם ממש להסתכל על מה שעושה, וזה רק הולך לסינון שורות תואמות סמוכות מהקלט. והאיש wc הולך להדפיס, מילת השורה החדשה, וספירת בתים עבור כל קובץ. והאחרון שאנחנו הולכים רוצים להשתמש הוא סוג, שהוא הולך רק כדי למיין שורות של קובץ txt. אם אני עושה איזה קובץ txt, names.txt, וזה רוב, טומי, יוסף, טומי, יוסף, RJ, רוב, מה שאני רוצה לעשות כאן הוא למצוא מספר השמות ייחודיים בקובץ זה. אז מה צריך להיות התשובה? >> [תלמיד] 4. >> כן. זה צריך להיות 4 מאז רוב, טומי, יוסף, RJ הם השמות הייחודיים רק בקובץ זה. הצעד הראשון, אם אני פשוט עושה את ספירת מילים בnames.txt, זה בעצם אומר לי הכל. זהו למעשה דפוס - בואו נראה, הגבר ב"ש - שורה החדשה, מילים, וספירת בתים. אם אכפת לי רק על קווים, אז אני יכול רק לעשות names.txt wc-l. אז זה שלב 1. אבל אני לא רוצה names.txt wc-l בגלל names.txt פשוט מכיל את כל השמות, ואני רוצה לסנן את כל אלה שאינם ייחודיים. אז אם אני עושה names.txt uniq, שלא ממש נותן לי מה שאני רוצה מכיוון שהשמות הכפולים הם עדיין שם. מדוע זה כך? למה uniq לא עושה מה שאני רוצה? [תלמיד] הכפילויות הן [לא נשמע] >> כן. זכור את הדף לאדם uniq אומר קווי התאמה סמוך סינון. הם לא צמודים, כך שזה לא יסנן אותם. אם אני למיין אותם ראשון, names.txt הסוג הולך לשים את כל הקווים הכפולים יחד. אז עכשיו names.txt המיון הוא ש. אני הולך רוצה להשתמש בו כקלט לuniq, שהוא | uniq. זה נותן לי יוסף, RJ, רוב, טומי, ואני רוצה להשתמש בו כקלט לwc-l, שהוא הולך לתת לי 4. כמו שכתוב כאן, מה אתה יכול להשתמש בצינור? אתה יכול לעשות הרבה דברים כמו באמצעות סדרה של פקודות שבו אתה משתמש בפלט של פקודה אחת כקלט לפקודה הבאה. אתה יכול לעשות הרבה דברים, הרבה דברים חכמים. שאלות? אוקיי. זהו זה לצינורות וניתוב מחדש. עכשיו אנחנו הולכים על דברים בפועל, חומר הקידוד. בתוך זה PDF, תראה פקודה זו, ואתה רוצה להריץ את הפקודה הזו במכשיר שלך. wget הוא הפקודה רק מקבל משהו מהאינטרנט, בעצם, כך wget וכתובת אתר זה. אם אתה נכנסת לכתובת אתר זו בדפדפן שלך, זה יהיה להוריד את הקובץ. אני פשוט הקלקתי עליו, כך שהוא הוריד את הקובץ בשבילי. אבל כתיבה של wget שהדבר שבתוך הטרמינל רק הולך להוריד אותו למסוף שלך. יש לי section5.zip, ואתה רוצה לפתוח section5.zip, שהוא הולך לתת לך תיקייה בשם section5, שהוא הולך להיות כל הקבצים שאנחנו הולכים להיות באמצעות היום בתוכו. כשמות קבצים של התוכניות אלה מציעים, הם קצת עגלה, אז המשימה שלך היא להבין מדוע באמצעות gdb. האם כל מי שיש להם להוריד / יודע איך להגיע אליהם הורדו למכשיר שלהם? אוקיי. ריצת ./buggy1, זה אומר אשמת פילוח (ליבה זרקה), שכל פעם שאתה מקבל segfault, זה דבר רע. באילו נסיבות אתה מקבל segfault? [תלמיד] ביטול הפנית מצביע null. >> כן. אז זה היא דוגמה אחת. ביטול הפנית מצביע null אתה הולך לקבל segfault. מה הוא אמצעי segfault אתה נוגע זיכרון אתה לא צריך להיות נוגע ללב. אז ביטול הפנית מצביע null נוגע כתובה 0, ובעצם, כל המחשבים בימים אומרים ש0 כתובת הוא זיכרון אתה לא צריך להיות נוגע ללב. אז זאת הסיבה לביטול הפנית תוצאות מצביע null בsegfault. כשאתה במקרה שלא ניתן לאתחל מצביע, אז יש לה ערך זבל, ולכן כאשר אתה מנסה dereference, ככל הנראה אתה נוגע זיכרון זה באמצע שום מקום. אם קורה לך מזל ואת ערך האשפה קרה להצביע על מקום במחסנית או משהו, ואז כשאתה dereference שמצביע שבו אתה לא מאותחל, שום דבר לא ישתבש. אבל אם זה הוא מצביע, אומר, איפשהו בין הערימה והערימה, או שזה מצביע בדיוק למקום שלא היה בשימוש על ידי התכנית שלך עדיין, אז אתה נוגע זיכרון אתה לא צריך לגעת ואתה segfault. כשאתה כותב פונקציה רקורסיבית וזה הרקורסיה פעמים רבות מדי והערימה שלך גדלה גדולה מדי ומתנגש המחסנית לתוך דברים שזה לא צריך להיות התנגשות עם, אתה נוגע זיכרון אתה לא צריך להיות נוגע ללב, אז אתה segfault. זה מה segfault הוא. זה גם מאותה הסיבה שאם יש לך מחרוזת כמו - בואו נחזור לתכנית הקודמת. בhello.c-אני פשוט הולך לעשות משהו אחר. char * s = "hello world!"; אם אני משתמש ב* s = משהו או של [0] = "X"; עד לשלום,. / שלום, למה שsegfault? למה זה segfault? מה היית מצפה שקורה? אם אני עשיתי printf ("% s \ n", S), מה היית מצפה שיודפס? [תלמיד] X שלום. >> כן. הבעיה היא שכאשר אתה מצהיר מחרוזת כמו זה, זה הוא מצביע שהולך ללכת על הערימה, ומה זה מצביע על זה הוא מחרוזת הנמצאת בזיכרון לקריאה בלבד. אז רק בשם הזיכרון, לקריאה בלבד, אתה צריך לקבל את הרעיון שאם תנסה לשנות את מה שיש בזיכרון לקריאה בלבד, אתה עושה משהו שאתה לא צריך לעשות עם זיכרון ואתה segfault. זהו למעשה הבדל גדול בין char * s וchar s []. אז char s [], עכשיו מחרוזת זה הולכת לשים על הערימה, והערימה היא לא לקריאה בלבד, מה שאומר שזה אמור לעבוד בסדר גמור. והוא לא. זכור שכשאני עושה char * s = "שלום העולם!", של עצמו בערימה אבל נקודתי s למקום אחר, וכי במקום אחר קורות להיות לקריאה בלבד. אבל char s [] הוא פשוט משהו במחסנית. אז זה עוד דוגמה לsegfault קורה. ראה ש./buggy1 הביא segfault. בתאוריה, אתה לא צריך להסתכל על buggy1.c באופן מיידי. במקום זאת, אנחנו מסתכלים על זה דרך gdb. שים לב שכאשר אתה מקבל אשמת פילוח (ליבה זרקה), אתה מקבל את הקובץ הזה מעל ליבה נקראת כאן. אם ls-l, נראה שהליבה היא בדרך כלל קובץ די גדול. זה מספר הבתים של הקובץ, כך שזה נראה כאילו זה קילובייט 250-משהו. הסיבה לכך היא כי מה שבעצם היא מזבלת הליבה כאשר תכנית הקריסות שלך, המדינה של זיכרון של התכנית שלך פשוט מקבל להעתיק ולהדביק לתוך קובץ זה. זה שהוא מושלך לתוך קובץ. תכנית זו, בזמן שהוא רץ, קרתה יש שימוש בזיכרון של 250 קילובייט ברחבי, ואז זה מה שקבל זרק לתוך קובץ זה. עכשיו אתה יכול להסתכל בקובץ שאם אנחנו עושים ליבת buggy1 gdb. אנחנו יכולים רק לעשות gdb buggy1, וזה יהיה פשוט להתחיל את gdb באופן קבוע, באמצעות buggy1 כקובץ הקלט שלה. אבל אם אתה עושה את ליבת buggy1 gdb, אז זה דווקא הולך להתחיל את gdb על ידי התבוננות שבקובץ הליבה. ואתה אומר gdb האמצעי buggy1 יודע שקובץ מגיע מליבת תכנית buggy1. אז gdb buggy1 הליבה הולכת להביא מייד לשם התכנית שקרתה לסיים. אנו רואים כאן תכנית הסתיימה עם 11 אות, אשמת פילוח. במקרה אנחנו רואים קו ההרכבה, אשר ככל הנראה היא לא מאוד מועיל. אבל אם תקליד BT או backtrace, שהולך להיות הפונקציה שנותן לנו את הרשימה של מסגרות המחסנית הנוכחיות שלנו. אז backtrace. זה נראה כאילו יש לנו שתי מסגרות מחסנית בלבד. הראשון הוא מסגרת המחסנית הראשית שלנו, והשני הוא מסגרת המחסנית לפונקציה זו שקורים לך להיות ב, שנראה כמו שיש לנו את קוד ההרכבה רק ל. אז בואו נחזור לתפקידו העיקרי שלנו, כדי לעשות את זה אנחנו יכולים לעשות מסגרת 1, ואני חושב שאנחנו יכולים לעשות גם למטה ו, אבל אני כמעט אף פעם לא עושה את - או למעלה. כן. מעלה ומטה. עד שאתה מביא את מסגרת ערימה אחת, את מביא אותך למסגרת מחסנית. אני נוטה שלא להשתמש בו. אני רק אומר שדווקא מסגרת 1, אשר עוברת לפריים שהכותרת 1. 1 מסגרת הוא הולך להביא אותנו למסגרת מחסנית ראשית, וזה כתוב פה שורת הקוד שקורים לך להיות בו. אם אנחנו רוצים עוד כמה שורות קוד, ניתן לומר רשימה, וזה הולך לתת לנו את כל השורות של קוד שסביבו. הקו שאנחנו segfaulted בהיה 6: if (strcmp ("CS50 סלעים", argv [1]) == 0). אם זה לא ברור עדיין, אתה יכול לקבל אותו ישר מכאן רק על ידי חשיבה למה זה segfaulted. אבל אנחנו יכולים לקחת את זה צעד אחד קדימה ואומרים, "למה שargv [1] segfault?" ההדפסה בואו argv [1], וזה נראה כאילו 0x0 זה, שהוא מצביע null. אנחנו strcmping CS50 סלעים וnull, ואז זה הולך segfault. ולמה הוא argv [1] null? [תלמיד] כי אנחנו לא נותנים לו שום טיעוני שורת פקודה. כן. אנחנו לא נותנים לו שום טיעוני שורת פקודה. אז ./buggy1 רק הולך להיות argv [0] יהיה ./buggy1. זה לא הולך להיות argv [1], כך שהולך segfault. אבל אם, לעומת זאת, אני עושה רק CS50, זה הולך להגיד אתה מקבל D כי זה מה שהוא אמור לעשות. כאשר מסתכלים על buggy1.c, זה אמור להדפיס "אתה מקבל D" - אם argv [1] לא "CS50 סלעים", "אתה מקבל D", אחר "אתה מקבל!" אז אם אנחנו רוצים, אנחנו צריכים את זה כדי להשוות כנכון, מה שאומר שהוא משווה ל 0. אז argv [1] צריך להיות "CS50 סלעים". אם אתה רוצה לעשות את זה בשורת הפקודה, אתה צריך להשתמש \ לברוח החלל. אז CS50 \ סלעים ואתה מקבלים! אם אתה לא עושה את הקו נטוי, למה זה לא עובד? [תלמיד] זה שני טיעונים שונים. >> כן. Argv [1] הולך להיות CS50, וargv [2] הולך להיות סלעים. אוקיי. עכשיו ./buggy2 הולך segfault שוב. במקום לפתוח אותו עם קובץ הליבה שלה, אנחנו פשוט לפתוח buggy2 ישירות, כך gdb buggy2. עכשיו, אם רק להפעיל את התכנית שלנו, אז זה הולך להגיד תכנית קבלה האות SIGSEGV, אשר segfault אות, וזה המקום שבו קרה לקרות. כאשר מסתכלים על backtrace שלנו, אנו רואים שאנחנו בoh_no הפונקציה, שנקרא על ידי dinky הפונקציה, שנקרא על ידי ינקי הפונקציה, שנקרא על ידי ראשי. אנחנו יכולים לראות גם את הטענות לפונקציות הללו. הטיעון לdinky וינקי היה 1. אם אנו מוסיפים את פונקצית oh_no, אנו רואים כי oh_no פשוט עושה char ** s = NULL; * של = "בום"; למה שייכשל? [תלמיד] אתה לא יכול dereference מצביע null? >> כן. זה רק אומר שלו ריק, ללא קשר אם זה קורה להיות char **, אשר, תלוי איך אתה מפרש אותו, זה יכול להיות מצביע למצביע למחרוזת או מערך של מחרוזות. זה s הוא NULL, אז * S הוא ביטול הפנית מצביע ריק, וכך זה הולך לקרוס. זו אחת הדרכים המהירות ביותר שאתה יכול segfault. זה פשוט הכרזה בטל ומצביע segfaulting באופן מיידי. זה מה שהוא עושה oh_no. אם תלכו עד פריים אחד, אז אנחנו הולכים להיכנס לפונקציה שנקראת oh_no. אני צריך לעשות את זה. אם לא יזין את הפקודה ואתה רק על Enter שוב, זה יהיה פשוט לחזור על הפקודה הקודמת שאתה בורח. אנחנו נמצאים במסגרת 1. רישום מסגרת זו, אנו רואים כאן היא הפונקציה שלנו. אתה יכול להכות שוב ברשימה, או שאתה יכול לעשות רשימה 20 וזה יהיה ברשימה יותר. Dinky הפונקציה אומר אם אני הוא 1, ואז ללכת לפונקצית oh_no, שייגש לפונקציה המתגנבת. ואנחנו יודעים שהוא 1 בגלל שאנחנו לקרות כדי לראות כאן שdinky נקרא בטענת 1. או שאתה יכול פשוט להדפיס אני והוא יגיד לי הוא 1. אנחנו נמצאים היום בהלא מזיקה, ואם אנחנו הולכים עד מסגרת אחרת, אנחנו יודעים שאנחנו בסופו של בינקי. עד. עכשיו אנחנו בינקי. רישום פונקציה זו - מהרשימה לפני המחצית קטעה אותי - זה התחיל כאם אני הוא 0, ואז אנחנו הולכים לקרוא לזה oh_no, אחר קוראים עלוב הזה. אנחנו יודעים שהייתי 1, אז זה נקרא עלוב הזה. ועכשיו אנחנו שוב בראשיים, ועיקריים פשוט הולכים להיות אני int = rand ()% 3; שרק הוא הולך לתת לך מספר אקראי שהוא או 0, 1 או 2. זה הולך לקרוא בינקי למספר הזה, וזה יחזיר 0. כאשר מסתכלים על זה, רק הליכה בתכנית באופן ידני מבלי להפעיל אותו באופן מיידי, יהיה עליך להגדיר נקודת הפסקה בראשית, מה שאומר שכאשר אנו מפעילים את התכנית התכנית פועלת עד שהוא פוגע בנקודת הפסקה. אז הפעלת התכנית, זה יפעל ואז זה הכה את הפונקציה העיקרית ולהפסיק לרוץ. עכשיו אנחנו בתוך ראשיים, וצעד או הבא הולכים להביא אותנו לשורה הבאה של קוד. אתה יכול לעשות את צעד או הבא. להכות הבא, עכשיו אני כבר מוגדר rand ()% 3, כדי שנוכל להדפיס את הערך של i, והיא אומרת לי היא 1. עכשיו זה כן משנה אם אנחנו משתמשים באים או צעד. אני מניח שזה היה חשוב בקודמו, אבל הייתי רוצה להשתמש הבא. אם אנו משתמשים בשלב, שאנחנו צועדים לפונקציה, מה שאומר שיסתכל על הדבר האמיתי שקורה בתוך ינקי. אם אנו משתמשים הבאים, אז זה אומר לעבור על הפונקציה ופשוט ללכת לשורה הבאה של קוד בפונקציה העיקרית שלנו. ממש כאן, על הקו הזה, הייתי שבבו אמר rand ()% 3; אם עשיתי צעד, זה הייתי הולך ליישום רנד ולהסתכל על מה שקורה שם, ואני יכול לעבור דרך פונקצית רנד. אבל לא אכפת לי על פונקצית רנד. אני רק רוצה ללכת לשורה הבאה של קוד בעיקרי, ולכן אני משתמש בא. אבל עכשיו אכפת לי פונקציה בינקי, אז אני רוצה להיכנס לזה. עכשיו אני בינקי. שורת הקוד הראשונה הולכת לומר אם (i == 0), אני לוקח צעד, אנחנו רואים שבסופו של דבר העלוב הזה. אם אנחנו, דברי רשימה, אנו רואים שבדקנו היא i = 0. אני לא שווה 0, אז זה נכנס למצב האחר, העומד להתקשר dinky (אני). אתה עלול להתבלבל. אם אתה רק מסתכל על הקווים האלה באופן ישיר, ייתכן שתחשוב אם (i == 0), בסדר, אז אני עשיתי צעד ועכשיו אני בdinky (ט), אתה עשוי לחשוב כי יש אומר שאני = 0 או משהו. לא, זה רק אומר שהוא יודע שהוא יכול לתקוע ישירות לdinky הקו (אני). כי אני לא 0, השלב הבא הוא לא הולך להסתיים באחר. אחר הוא לא קו זה הולך לעצור ב. זה פשוט הולך לשורה הבאה זה באמת יכול לבצע, שהוא עלוב (אני). צועדים לתוך dinky (ט), אנו רואים אם (i == 1). אנחנו יודעים שאני = 1, ולכן כאשר אנו דורכים, אנחנו יודעים שאנחנו הולכים בסופו של oh_no כי אני = 1 קורא oh_no הפונקציה, שאתה יכול להיכנס אליו, שהולך להגדיר char ** s = ל NULL ומייד "בום". ואז באמת מסתכל על יישום buggy2, זה, אני פשוט מקבל אותו מספר אקראי - 0, 1 או 2 - קורא בינקי, שאם אני הוא 0 שהיא ממכנת oh_no, אחר היא ממכנת הלא מזיקה, אשר מגיע עד לכאן. אם אני הוא 1, השיחה oh_no, אחר קוראים חלקלק, שיבואו לכאן, אם אני הוא 2, מכנה oh_no. אני אפילו לא חושב שיש דרך - האם מישהו רואה את הדרך של עשייה זו תכנית שלא segfault? מפני שאם חסר לי משהו, אם אני הוא 0, אתה segfault באופן מיידי, אחר אתה הולך לפונקציה שאם אני הוא 1 segfault, אחר אתה הולך לתפקיד שבו אם אני הוא 2 segfault. אז לא משנים מה אתה עושה, אתה segfault. אני מניח שדרך אחת של תיקון זה יהיה במקום לעשות char ** s = NULL, אתה יכול malloc מקום שלמחרוזת. אנחנו יכולים לעשות malloc (sizeof) - sizeof מה? [תלמיד] (char) * 5? >> האם זה נראה נכון? אני מניח שזה יעבוד אם אני ממש רציתי אותו, אבל זה לא מה שאני מחפש. תראה את הסוג של. בואו נוסיף * int, אז int * x. הייתי עושה malloc (sizeof (int)). או אם אני רוצה מערך של 5, שהייתי עושה (sizeof (int) * 5); מה אם יש לי ** int? מה הייתי malloc? [תלמיד] גודל של המצביע. >> כן. (Sizeof (int *)); אותו דבר כאן. אני רוצה (sizeof (char *)); זה הולך להקצות שטח למצביע שמצביע על "בום". אני לא צריך להקצות שטח ל" בום "עצמו כי זה בעצם שווה ערך למה שאמרתי קודם של char * x = "בום". "בום" כבר קיים. זה קורה לקיים באזור לקריאה בלבד של זיכרון. אבל זה כבר קיים, מה שאומר שהקו הזה של קוד, אם S הוא ** char, אז * זה * הוא char ואתה קובע * char זה כדי להצביע על "בום". אם אני רוצה להעתיק "בום" לים, אז לא הייתי צריך להקצות שטח לים. אני אעשה * s = malloc (sizeof (char) * 5); למה 5? למה לא 4? זה נראה כמו "בום" הוא 4 תווים. >> [תלמיד] דמות האפס. כן. כל המחרוזות שלך הולכים צריך תו null. עכשיו אני יכול לעשות משהו כמו strcat - מה תפקידו להעתקת מחרוזת? [תלמיד] פ י? >> Strcpy. strcpy אדם. אז strcpy או strncpy. strncpy הוא קצת יותר בטוח מאז שאתה יכול לציין בדיוק כמה תווים, אבל כאן זה לא משנה כי אנחנו יודעים. אז strcpy ולהסתכל בטענות. הטיעון הראשון הוא היעד שלנו. הטיעון השני הוא המקור שלנו. אנחנו הולכים להעתיק ל* היעד שלנו זה המצביע "בום". למה אולי אתה רוצה לעשות את זה עם strcpy במקום רק את מה שהיינו לפני * משל = "בום"? יש סיבה לכך אולי כדאי לך לעשות את זה, אבל מה היא הסיבה ש? [תלמיד] אם אתה רוצה לשנות משהו ב" בום ". >> כן. עכשיו אני יכול לעשות משהו כמו של [0] = "X"; משום שנקודתי s לערימה ושטח שעל הערימה שזה מצביעות על הוא מצביע למקום נוסף על הערמה, אשר האחסון "בום". אז עותק זה של "בום" הוא להיות מאוחסן בערימה. מבחינה טכנית יש שני עותקים של "בום" בתכנית שלנו. יש הראשון שרק ניתנים על ידי המתמיד הזה "בום" המחרוזת, והעותק השני של "בום", strcpy יצר עותק של "בום". אבל העותק של "בום" הוא להיות מאוחסן בערימה, והערימה אתה חופשי לשנות. הערימה היא לא לקריאה בלבד, כך שמשמעות היא שנ [0] הוא הולך לתת לך לשנות את הערך של "בום". זה הולך לתת לך לשנות את התווים האלה. שאלות? אוקיי. על מנת להזיז buggy3, gdb buggy3 בואו. אנחנו פשוט להפעיל אותו ואנחנו רואים שאנחנו מקבלים segfault. אם אנחנו backtrace, יש רק שתי פונקציות. אם אנחנו הולכים עד לפונקציה העיקרית שלנו, אנו רואים שsegfaulted בקו הזה. אז פשוט מסתכל על שורה זו, ל( int קו = 0; fgets החומר הזה עושה NULL לא שווה; שורה + +). המסגרת הקודמת שלנו נקראת _IO_fgets. אתה תראה את זה הרבה עם פונקציות C מובנהיות, כי כאשר אתה מקבל segfault, יהיו שמות פונקציות באמת מסתוריים כמו _IO_fgets זה. אבל זה הולך להתייחס לשיחת fgets זה. איפשהו בתוך כאן, אנו segfaulting. אם אנחנו מסתכלים על הטיעונים לfgets, אנחנו יכולים להדפיס חיץ. בואו להדפיס כ-- הו, לא. הדפסה היא לא הולכת לעבוד בדיוק כמו שאני רוצה אותו. בואו נסתכל על התכנית בפועל. חיץ הוא מערך תווים. זה מערך תווים של 128 תווים. לכן, כאשר אני אומר מאגר ההדפסה, זה הולך להדפיס 128 התווים האלה, אני מניח שזה מה שצפוי. מה שאני מחפש הוא להדפיס את הכתובת של מאגר, אבל זה לא באמת אומר לי הרבה. לכן, כאשר אני במקרה לומר עד כאן חיץ x, זה מראה לי 0xbffff090, אשר, אם אתה זוכר מקודם או איזו נקודה, Oxbffff נוטה להיות אזור מחסנית-ish. הערימה נוטה להתחיל איפשהו מתחת 0xc000. רק על ידי רואה הכתובת הזאת, אני יודע שחיץ שקורה בערימה. הפעלה מחדש של התכנית שלי, לרוץ, למעלה, חיץ שראינו היה זה רצף של תווים שהם פחות או יותר חסרי משמעות. אז הדפסת קובץ, מה קובץ נראה? [תלמיד] ריק. >> כן. קובץ הוא מסוג קובץ *, כך שזה הוא מצביע, והערך של מצביע הוא ריק. אז fgets הולך לנסות לקרוא שמהמצביע בדרך עקיפה, אבל כדי לגשת מצביע זה, זה צריך dereference. לחלופין, על מנת לגשת מה זה צריך להיות מכוון ל, זה dereferences זה. אז זה ביטול הפנית מצביע null וזה segfaults. הייתי יכול להפעיל מחדש את זה שם. אם נשבר בנקודה העיקרית שלנו ולרוץ, השורה הראשונה של קוד היא char * filename = "nonexistent.txt"; זה צריך לתת לי רמז די גדול, מדוע תכנית זו תיכשל. ההקלדה הבאה מביאה אותי לשורה הבאה, שבו אני פותח את הקובץ הזה, ואז אני מקבל מייד לקו שלנו, שבו ברגע שאפגע בפעם הבאה, זה הולך segfault. האם מישהו רוצה לזרוק את הסיבה שאנו יכולים להיות segfaulting? [תלמיד] קובץ לא קיים. >> כן. זה אמור להיות רמז כי בכל פעם שאתה פותח קובץ אתה צריך לבדוק שהקובץ באמת קיים. אז הנה, "nonexistent.txt"; כשקובץ fopen לקריאה, אז אנחנו צריכים לומר אם (הקובץ == NULL) ואומרים printf ("קובץ לא קיים!" או - יותר נכון - קובץ); תמורה 1; אז עכשיו אנחנו בודקים אם זה NULL לפני למעשה ממשיך ומנסה לקרוא מן הקובץ. אנחנו יכולים לשנות את זה רק כדי לראות שזה עובד. אני מתכוון לכלול בשורה חדשה. אז עכשיו nonexistent.txt לא קיים. אתה תמיד צריך לבדוק לדברים מהסוג הזה. אתה תמיד צריך לבדוק אם fopen מחזיר NULL. אתה תמיד צריך לבדוק כדי לוודא שmalloc לא מחזיר NULL, אחרת אתה segfault. עכשיו buggy4.c. פועל. אני מנחש שזה מחכה לקלט או לולאות אינסופיות, אולי. כן, זה לולאות אינסופיות. אז buggy4. זה נראה כאילו אנחנו לולאות אינסופיות. אנחנו יכולים לשבור בראשי, להפעיל את התכנית שלנו. בgdb, כל עוד אתה משתמש בקיצור הוא חד משמעי או קיצורים מיוחדים שהם מספקים עבורך, אז אתה יכול להשתמש n להשתמש הבא, במקום להקליד את כל הדרך הבאה. ועכשיו, אחרי שפגעתי n פעם אחת, אני יכול פשוט מקיש על Enter כדי להמשיך הבא במקום שיש להכות n זן, זן n, n Enter. זה נראה כאילו אני באיזה לולאה לזה הגדרת מערך [i] ל 0. זה נראה כאילו אני לא אפרוץ מזה ללולאה. אם אני מדפיס אני, אז הוא 2, אז אני אלך בא. אני אדפיס אני, אני הוא 3, אז אני אלך בא. אני אדפיס אני ואני הוא 3. בשלב בא, להדפיס, i היא 4. למעשה, ההדפסה sizeof (מערך), ולכן גודל של מערך הוא 20. אבל זה נראה כאילו יש איזה פקודת gdb מיוחדת להולך עד שמשהו קורה. זה כמו הגדרת תנאי על הערך של המשתנה. אבל אני לא זוכר מה זה. אז אם אנחנו ממשיכים הלאה - מה אמר? בשביל מה הבאת את? [תלמיד] האם להציג אני מוסיף - >> כן. אז להציג אני יכול לעזור. אם אנחנו רק להציג לי, זה יהיה לשים כאן מה הערך של i הוא אז אני לא צריך להדפיס אותו בכל פעם. אם אנחנו פשוט להמשיך הבאים, אנו רואים 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5. משהו מאוד לא בסדר קורה, ואני נמצא באיפוס ל 0. כאשר מסתכלים על buggy4.c, אנו רואים כל מה שקורה הוא מערך int [5]; עבור (i = 0; i <= sizeof (מערך); אני + +) מערך [i] = 0; מה שאנחנו כן רואים שלא בסדר כאן? כרמז, כשאני עושה gdb buggy4 - בואו לשבור ראשי, טווח - אני לא ההדפסה sizeof (מערך) רק כדי לראות מה המצב שבו אני צריך סוף סוף אפרוץ. איפה אני? האם אני רץ? אני לא הכרזתי עדיין. אז להדפיס sizeof (מערך) וזה 20, אשר צפוי מאז המערך שלי הוא בגודל 5 וזה של 5 מספרים שלמים, לכן כל הדבר צריך להיות 5 * בתי sizeof (int), שבו sizeof (int) נוטה להיות 4. אז sizeof (מערך) הוא 20. מה זה צריך להיות? [תלמיד] לחלק ב sizeof (int). >> כן, / sizeof (int). זה נראה כאילו יש עדיין בעיה כאן. אני חושב שזה צריך פשוט להיות < מכיוון שזה פחות או יותר תמיד <ולא <=. עכשיו בואו נחשוב למה זה היה שבור ממש. למישהו יש ניחושים למה הייתי לאפס ל 0 דרך כל איטרציה של הלולאה? הדבר היחיד שנותר מכאן שקורה הוא שהמערך [i] היא להיות מוגדרת 0. אז איכשהו, הקו הזה של קוד גורם int שלנו אני להיות מוגדר 0. [תלמיד] האם זה יכול להיות בגלל שזה מבטל את הזיכרון של חלק זה שלי כאשר הוא חושב שזה האלמנט הבא של מערך? >> [אודן] כן. כאשר אנחנו הולכים מעבר לסוף המערך שלנו, איכשהו מרחב שבו אנו דריסתו דריסת הערך של i. ולכן אם אנחנו מסתכלים לתוך buggy4, לשבור, ריצה עיקרית, בואו להדפיס את הכתובת של i. זה נראה כמו שזה bffff124. עכשיו בואו להדפיס את הכתובת של מערך [0]. 110. מה לגבי [1]? 114. [2], 118. 11c, 120. מערך [5] הוא bfff124. אז מערך [5] יש אותה הכתובת כמו שאני, מה שאומר שהמערך [5] הוא אני. אם יש להם את אותה כתובת, שהם אותו הדבר. לכן, כאשר אנו קובעים מערך [5] ל 0, אנו קובעים שכדי 0. אם אתה חושב על זה במונחים של המחסנית ו, אני int מוצהר ראשון, מה שאומר שאני מקבל קצת מקום במחסנית. אז מערך [5] מוקצה, אז 20 בתים מוקצים על המחסנית. אז אני מקבל מוקצה תחילה, ולאחר מכן 20 בתים אלה לקבל שהוקצו. אז אני קורה ממש לפני המערך, ובגלל הדרך, כמו שאמרתי בשבוע שעבר, שבו מבחינה טכנית המחסנית גדלה כלפי מטה, כאשר אינדקס לתוך מערך, מובטחים לנו כי העמדה 0 במערך תמיד קורה לפני המיקום הראשון במערך. זה סוג של איך שלפתי אותה בשבוע שעבר. שים לב כי בחלק התחתון יש לנו כתובה 0 ובראש יש לנו מקס כתובת. הערימה מתרחבת תמיד כלפי מטה. נניח שאנו מקצים לי. אנו מקצים מספר שלמים אני, מה שאומר בואו נגיד עד כאן אני מקבל מספר שלם שהוקצה. ואז אנו מקצים המערך שלנו של 5 מספרים שלמים, מה שאומר שמתחת לזה, מאז שהערימה גדלה למטה, 5 המספרים שלמים האלה, מי שהוקצה. אבל בגלל כמה מערכים יעבדו, אנחנו מובטחים שהמיקום הראשון במערך תמיד יש כתובת פחות מהדבר השני במערך. אז 0 עמדת המערך תמיד צריך לקרות ראשון בזיכרון, ואילו עמדת מערך 1 צריכה לקרות אחרי ש ועמדת מערך 2 צריכה לקרות אחרי זה, מה שאומר ש0 עמדת המערך יקרו איפשהו כאן למטה, עמדת מערך 1 תקרה לעיל כי משום שנע מעלה פירוש כתובות גבוהות מאז הכתובת המרבית היא עד כאן. אז מערך [0] כאן למטה, מערך [1] עד כאן, מערך [2] עד כאן, מערך [3] עד כאן. שים לב איך לפני שהוקצינו שלם אני כל הדרך עד לכאן, בעודנו נעים יותר ויותר לתוך המערך שלנו, אנו מתקרבים יותר ויותר למספר השלם אני. זה פשוט כל כך קורה כי המערך [5], שהוא מעבר לעמדה אחת המערך שלנו, הוא בדיוק איפה שלם יצאתי לי להיות מוקצה. אז זה הנקודה שבה אנחנו במקרה להכות את החלל במחסנית שהוקצה למספר השלם i, ואנחנו קביעה כי ל 0. ככה זה עובד. שאלות? כן. [תלמיד] לא משנה. אוקיי. [תלמיד] איך נמנע מאלה מעין שגיאות? אלה מעין שגיאות? אל תשתמש בשפת תכנות C כ. השתמש בשפה זו יש גבולות מערך בדיקה. כל עוד אתה זהיר, אתה פשוט צריך להימנע מהעבר מגבולות המערך שלך. [תלמיד] אז הנה כשעברנו את גבולות מערכך - [אודן] זה שבו דברים מתחילים להשתבש. >> [תלמיד] אה, בסדר. כל עוד אתה נשאר בתוך הזיכרון שהוקצה למערך שלך, אתה בסדר. אבל ג'לא עושה בדיקת שגיאות. אם אני עושה את המערך [1000], זה יהיה בשמחה פשוט לשנות כל מה שקורה - זה הולך לתחילת המערך, אז זה הולך אחרי 1000 עמדות ומניח אותו על 0. זה לא עושה כל בדיקה שהו, זה לא באמת יש 1000 דברים בזה. 1000 הם הרבה מעבר למה שאני צריך שינוי, בעוד ג'אווה או משהו שתקבלי מערך מאינדקס של גבולות או מדד מתוך גבולות חריג. זו הסיבה שהרבה שפות ברמה גבוהות יותר יש את הדברים האלה שם אם אתה רוצה ללכת מעבר לגבולות של המערך, אתה נכשל כך שאתה לא יכול לשנות את הדברים מתחתיך ואז דברים ילכו הרבה יותר גרועים מסתם מקבל חריג אמר שהלכת מעבר לסוף המערך. [תלמיד] וכך צריך יש לנו רק שיניתי את <= רק > [אודן] כן. זה צריך להיות > [תלמיד] ימני. עוד שאלות? אוקיי. [תלמיד] יש לי שאלה. >> כן. [תלמיד] מהו משתנה המערך בפועל? [אודן] כמו מה הוא מערך? המערך עצמו הוא סמל. זה רק את הכתובת של תחילת שנתי ה 20 הבתים שאנו מפנים. אתה יכול לחשוב על זה כמצביע, אך הוא מצביע קבוע. ברגע שהדברים מקבלים הידור, המערך משתנה כבר לא קיים. [תלמיד] אז איך זה למצוא את גודלו של מערך? גודלו של מערך מתייחס לגודל של שהבלוק שהסמל שמתייחס ל. כשאני עושה משהו כמו printf ("% p \ n", מערך); בואו להפעיל אותו. מה הדבר האחרון שעשיתי בסדר? 'המערך' המערך הכריז כאן. אה, עד כאן. הצלצול הוא חכם, וזה קורה ללב שאני הכרזתי מערך כ5 אלמנטים אבל אני אינדוקס למקום 1000. זה יכול לעשות את זה כי אלה הם רק בקבועים. זה יכול להגיע רק עד נקודה בלב שאני עומד מעבר לגבולות של המערך. אבל שם לב שהיינו לנו קודם לכן, כאשר אני להיות שגוי, אין זה אפשרי לקבוע כמה ערכים שאני יכול לקחת על, כך שזה לא יכול לקבוע שאני הולך אל מעבר לסוף המערך. זה פשוט קלאנג להיות חכם. אבל עכשיו לעשות buggy4. אז מה עוד אני עושה לא בסדר? במרומז הכרזת פונקצית ספרייה 'printf'. אני הולך לרוצה # לכלול . אוקיי. כעת פועל buggy4. להדפיס את הערך של המערך כמו שעשיתי כאן, להדפיס אותו כמצביע משהו הדפסים שנראה כך - bfb8805c - שהוא חלק הכתובת זה באזור מחסנית-ish. מערך עצמו הוא כמו מצביע, אבל זה לא מצביע בפועל, מאז מצביע רגיל אנחנו יכולים לשנות. מערך הוא רק חלק קבוע. 20 בלוקים של זיכרון מתחילים ב 0xbfb8805c כתובת. אז bfb8805c דרך כתובת זו +20- או אני מניח -20 - הוא כל הזיכרון שהוקצה למערך זה. מערך, משתנה עצמו אינו מאוחסן בכל מקום. כשאתה קומפילציה, מהדר - גל יד בזה - אבל מהדר פשוט להשתמש בה יודע מערך להיות. הוא יודע איפה מערך שמתחיל, ולכן הוא יכול תמיד פשוט לעשות את דברים במונחים של קיזוז שמההתחלה. היא לא צריכה לייצג את עצמו משתנה מערך. אבל כשאני עושה משהו כזה int * p = מערך, עכשיו p הוא מצביע שמצביע שמערך, ועכשיו p אכן מקיים במחסנית. אני חופשי לשנות p. אני יכול לעשות p = malloc. אז זה המקור הצביע למערך, עכשיו הוא מצביע למקום כלשהו בערימה. אני לא יכול לעשות malloc = מערך. אם קלאנג הוא חכם, הוא צועק עליי מייד את הבת. למעשה, אני די בטוח שgcc הייתי עושה גם. אז סוג המערך 'int [5]' אינו ניתן להעברה. לא ניתן להקצות משהו לסוג מערך כי המערך הוא פשוט קבוע. זהו סמל שההפניות 20 הבתים הללו. אני לא יכול לשנות את זה. [תלמיד] ואיפה הגודל של המערך מאוחסן? [אודן] זה לא מאוחסן בכל מקום. זה כאשר זה הידור. אז איפה הוא בגודל של מערך מאוחסן? אתה יכול להשתמש בsizeof (מערך) רק בתוך הפונקציה שהמערך הכריז על עוצמה. אז אם אני עושה את תפקיד כלשהו, ​​foo, ואני עושה (int מערך []) printf ("% d \ n", sizeof (מערך)); ואז אני קורא כאן foo (מערך); בתוך פונקציה זו - בואו להפעיל אותו. זה קלאנג להיות חכם שוב. הוא אומר לי שעל פרמטר sizeof פונקצית מערך יחזור גודלו של "* int". זו תהיה שגיאה אם ​​זה לא מה שרציתי שקורה. בואו בעצם לכבות Werror. אזהרה. אזהרות הן בסדר גמורות. זה עדיין יהיה לקמפל את זמן שיש לו אזהרה. a.out. / הולך להדפיס 4. האזהרה שנוצרה היא אינדיקציה ברורה של מה השתבש. מערך int זה רק הולך להדפיס sizeof (int *). גם אם אני מניח את המערך [5] בכאן, זה עדיין רק הולך להדפיס sizeof (int *). אז ברגע שאתה עובר אותו לפונקציה, ההבחנה בין מערכים ומצביעים הוא אפסי. זה קורה להיות מערך שהוכרז על הערימה, אבל ברגע שאנחנו מעבירים את הערך ש, ש0xbf בלה, בלה, בלה לפונקציה זו, אז מצביע זה מצביע שמערך על המחסנית. אז זה אומר שsizeof חל רק בפונקציה שהמערך הוכרז, מה שאומר שכאשר אתה קומפילצית פונקציה זו, כאשר קלאנג עובר בפונקציה זו, שהיא רואה מערך הוא מערך int בגודל 5. אז הוא רואה sizeof (מערך). טוב, זה 20. זה בעצם איך sizeof בעצם עובד כמעט בכל המקרים. Sizeof הוא לא פונקציה, זה מפעיל. אתה לא קורא את פונקצית sizeof. Sizeof (int), המהדר פשוט יתרגם את זה עד 4. יש את זה? אוקיי. [תלמיד] אז מהו ההבדל בין sizeof (מערך) ובעיקרי בfoo? הסיבה לכך היא שאנחנו אומרים sizeof (מערך), שהוא מסוג int *, ואילו המערך כאן למטה הוא לא מסוג int *, זה מערך int. [תלמיד] לכן אם היה הפרמטר במערך [] במקום מערך * int, זה היה אומר שאתה עדיין יכול לשנות את המערך כי עכשיו זה מצביע? [אודן] כמו זה? >> [תלמיד] כן. אתה יכול לשנות את המערך בתוך הפונקציה עכשיו? [אודן] אתה יכול לשנות את המערך בשני המקרים. בשני המקרים האלה אתה חופשי לומר מערך [4] = 0. [תלמיד] אבל אתה יכול להפוך את הנקודה של מערך למשהו אחר? [אודן] הו. כן. בכל מקרה - >> [תלמיד] כן. [אודן] ההבחנה בין מערך [] ומערך * int, אין כזה. גם אתה יכול לקבל כמה מערך רב ממדי בפה עבור חלק התחביר נוח, אבל זה עדיין רק מצביע. זה אומר שאני חופשי לעשות מערך = malloc (sizeof (int)); ועכשיו להצביע במקום אחר. אבל בדיוק כמו איך זה עובד לנצח, ותמיד, שינוי מערך זה בכך שהוא מצביע על משהו אחר אינו משנה את המערך הזה לכאן, כי זה עותק של הוויכוח, זה לא מצביע שלטענה. ובעצם, זה רק כאינדיקציה נוספת שזה בדיוק אותו הדבר - אנחנו כבר ראינו מה הדפסי מערך הדפסה - מה אם אנחנו מדפיסים את הכתובת של המערך או את הכתובת של הכתובת של המערך לאף אחד מאלה? בואו נתעלם מזה. אוקיי. זה בסדר גמור. עכשיו זה פועל. / A.out. מערך הדפסה, ולאחר מכן להדפיס את הכתובת של המערך, הוא אותו הדבר. מערך פשוט לא קיים. הוא יודע כשאתה מדפיס מערך, אתה מדפיס את הסמל המתייחס ל20 הבתים הללו. הדפסת הכתובת של המערך, טוב, מערך לא קיים. זה לא חייב כתובת, אז זה פשוט מדפיס את הכתובת של 20 הבתים הללו. ברגע שאתה לאסוף למטה, כמו בbuggy4 ההידור שלך. / A.out, מערך הוא אפסי. מצביעים קיימים. מערכים לא. אובניים זיכרון המייצג את המערך עדיין קיים, אבל המערך ומשתנה מהסוג משתנה לא קיימים. אלה הם כמו ההבדלים העיקריים בין מערכים ומצביעים הם ברגע שאתה לבצע שיחות פונקציה, אין הבדל. אבל בתוך הפונקציה שהמערך עצמו הצהיר, sizeof עובד אחרת מאז שאתה מדפיס את גודל הקוביות במקום בגודל של הסוג, ואתה לא יכול לשנות את זה כי זה סמל. הדפסת הדבר ואת כתובתו של הדבר מדפיסה את אותו הדבר. וזה פחות או יותר אותו. [תלמיד] האם אתה יכול לומר שעוד פעם אחת? שאולי פספסתי משהו. מערך הדפסה וכתובת של מערך מדפיסים את אותו דבר, ואילו, אם תדפיס מצביע לעומת הכתובת של המצביע, דבר אחד ידפיס את הכתובת של מה שאתה מצביע, האחרים מדפיס את הכתובת של המצביע על הערימה. באפשרותך לשנות מצביע, אתה לא יכול לשנות את סמל מערך. ומצביע sizeof הולך להדפיס בגודל של סוג המצביע. אז * p sizeof (p) הולך להדפיס int 4, אבל המערך [5] ההדפסה sizeof (מערך) הולך להדפיס int 20. [תלמיד] אז int מערך [5] ידפיס 20? >> כן. זו הסיבה הפנימית של buggy4 כשזה היה אמור להיות (מערך) sizeof זה עושה לי <20, וזה לא מה שרצינו. אנחנו רוצים i <5. >> [תלמיד] אוקיי. [אודן] ואז ברגע שאתה מתחיל לעבור בפונקציות, אם אנחנו לא int * p = מערך; בתוך פונקציה זו, אנו יכולים להשתמש בעיקרון עמ ומערך בדיוק באותן הדרכים, פרט לבעית sizeof ובעיה בשינוי. אבל p [0] = 1; הוא זהים אומר מערך [0] = 1; וברגע שאנחנו אומרים foo (מערך); או foo (p); בתוך פונקצית foo, זו אותה השיחה פעמים. אין הבדל בין שתי השיחות הללו. כולם טובים בזה? אוקיי. יש לנו 10 דקות. אנחנו ננסה להשיג באמצעות תכנית Typer ההאקר הזה, אתר זה, שיצא בשנה שעברה או משהו כזה. אבל זה אמור להיות כמו שאתה מקליד באופן אקראי וזה מדפיס את - לא משנה מה קובץ זה קורה לטען הוא מה זה נראה כמו שאתה מקליד. זה נראה כמו איזה קוד של מערכת הפעלה. זה מה שאנחנו רוצים ליישם. אתה צריך להיות בהפעלת ינארי השם hacker_typer שלוקח בויכוח אחד, את הקובץ "סוג ההאקר". ריצת ההפעלה צריכה לנקות את המסך ולאחר מכן להדפיס את תו אחד מהקובץ מחוסר הכרה בכל פעם שמשתמש לוחץ על מקש. אז לא משנה איזה מקש אתה לוחץ, זה צריך לזרוק ובמקום להדפיס דמות מהקובץ זה הוויכוח. אני די הרבה אגיד לך מה הדברים שאנחנו הולכים צריכים לדעת הם. אבל אנחנו רוצים לבדוק את ספריית termios. אני מעולם לא השתמשתי בספרייה זו בכל החיים שלי, אז יש לו מטרות מאוד מינימאליות. אבל זה הולך להיות הספרייה שאנחנו יכולים להשתמש בו כדי להשליך על האופי אתה מכה כאשר אתה מקליד לתוך סטנדרטי פנימה אז hacker_typer.c, ואנחנו הולכים לרוצים # לכלול . כאשר מסתכלים על דף האדם לtermios - אני מנחש מסוף מערכת הפעלה או שזה משהו - אני לא יודע איך לקרוא אותו. כאשר מסתכלים על זה, זה אומר לכלול 2 הקבצים האלה, ולכן אנחנו נעשה את זה. דבר הראשון ראשון, אנחנו רוצים לקחת בטענה אחת, שהוא הקובץ שאנחנו צריכים לפתוח. אז מה אני רוצה לעשות? כיצד ניתן לבדוק כדי לראות שיש לי טענה אחת? [תלמיד] אם argc שווה את זה. >> [אודן] כן. אז אם (argc = 2!) Printf ("שימוש:% s [קובץ כדי לפתוח]"). אז עכשיו אם אני מפעיל את זה מבלי לספק נימוק שני - אוי, אני צריך את הקו החדש - אתה רואה את זה אומר שימוש:. / hacker_typer, ולאחר מכן את הטענה השנייה צריכה להיות קובץ שאני רוצה לפתוח. עכשיו מה עליי לעשות? אני רוצה לקרוא מקובץ זה. כיצד ניתן לקרוא מקובץ? [תלמיד] אתה פותח אותו לראשונה. >> כן. אז fopen. מה fopen נראה? [תלמיד] שם קובץ. >> [אודן] שם הקובץ הולך להיות argv [1]. [תלמיד] ואז מה שאתה רוצה לעשות איתו, ולכן - >> [אודן] כן. אז אם אתה לא זוכר, אתה יכול פשוט לעשות fopen אדם, איפה זה הולך להיות דרך * char const path הוא שם קובץ, מצב * char const. אם קורה לך לא זוכר מה מצבו, ואז אתה יכול להסתכל על מצב. בתוך דפי אדם, תו הקו הוא מה שאתה יכול להשתמש בו כדי לחפש דברים. אז אני מקליד / מצב כדי לחפש מצב. n ו-N הם מה שאתה יכול להשתמש בו כדי לעבור בין תוצאות החיפוש. הנה זה אומר נקודתי מצב הטיעון למחרוזת מתחיל עם אחד מהרצפים הבאים. אז r, קובץ טקסט פתוח לקריאה. זה מה שאנחנו רוצים לעשות. לקריאה, ואני רוצה לשמור את זה. הדבר הולך להיות * קובץ. עכשיו מה שאני רוצה לעשות? תן לי שני. אוקיי. עכשיו מה שאני רוצה לעשות? [תלמיד] בדקו אם זה NULL. >> [אודן] כן. בכל פעם שאתה פותח את קובץ, ודא שאתה בהצלחה תוכל לפתוח אותו. עכשיו אני רוצה לעשות את הדברים האלה termios בו אני רוצה לקרוא את ההגדרות הנוכחיות שלי 1 ולשמור אותם לתוך משהו, אז אני רוצה לשנות את ההגדרות שלי לזרוק כל תו שאני מקליד, ואז אני רוצה לעדכן את ההגדרות הללו. ואז בסוף התכנית, אני רוצה לחזור להגדרות המקוריות שלי. אז struct הולך להיות מהסוג termios, ואני הולך לרוצה שניים מאלה. הראשון הולך להיות current_settings, ואז הם הולכים להיות hacker_settings. ראשית, אני הולך ברצונך לשמור את ההגדרות הנוכחיות שלי, אז אני הולך לרוצה לעדכן hacker_settings, ולאחר מכן דרך בסוף התכנית שלי, אני רוצה לחזור להגדרות נוכחיות. אז שמירת הגדרות נוכחיות, אופן שבו עובד, אנחנו termios אדם. אנו רואים שיש לנו tcsetattr זה int, int tcgetattr. אני עובר בtermios struct ידי המצביע שלו. כך ייראו הוא - רצינות, כבר שכחה מה הפונקציה נקראה. להעתיק ולהדביק אותו. אז tcgetattr, אז אני רוצה לעבור בstruct שאני חוסך במידע, שהולך להיות current_settings, וטענתו הראשונה היא מתארת ​​קובץ לדבר אני רוצה להציל את התכונות של. מה הוא מתאר הקובץ היא כמו כל פעם שתפתח את קובץ, הוא מקבל מתאר קובץ. כשfopen argv [1], הוא מקבל מתאר קובץ שאתה התייחסות כל פעם שאתה רוצה לקרוא או לכתוב אליו. זה לא מתאר קובץ שאני רוצה להשתמש כאן. ישנם שלושה מתארי קובץ שיש לך כברירת מחדל, שהנם סטנדרט ב, את התקן, ושגיאה סטנדרטית. כברירת מחדל, אני חושב שזה סטנדרט בהוא 0, פלט הסטנדרטי הוא 1, ושגיאה סטנדרטית היא 2. אז מה אני רוצה לשנות את ההגדרות של? אני רוצה לשנות את ההגדרות של כל פעם שאני מכה אופי, אני רוצה שזה זורק את האופי שבמקום להדפיס אותו על המסך. מה זרם - סטנדרטי ב, מתוך סטנדרטיים, או שגיאה סטנדרטית - מגיב לדברים כשאני מקליד במקלדת? >> [תלמיד] תקן פנימה >> כן. אז או שאני יכול לעשות 0 או שאני יכול לעשות stdin. אני מקבל current_settings של תקן פנימה עכשיו אני רוצה לעדכן את ההגדרות הללו, אז קודם אני אעתיק לhacker_settings מה current_settings שלי. ואיך עבודת structs היא שזה יהיה פשוט להעתיק. זה מעתיק את כל השדות, כפי שהיית מצפה. עכשיו אני רוצה לעדכן חלק מהשדות. כאשר מסתכלים על termios, היית צריך לקרוא את זה הרבה רק כדי לראות מה שאתה רוצה לחפש, אבל את הדגלים שאתה הולך רוצה לחפש הם הד, כך ECHO תווי קלט אקו. ראשית אני רוצה להגדיר - רצינות, כבר שכח מה את השדות. זה מה שנראה כמו struct. אז מצבי קלט אני חושב שאנחנו רוצים לשנות. אנחנו נסתכל על הפתרון כדי לוודא שזה מה שאנחנו רוצים לשנות. אנחנו רוצים לשנות את lflag כדי למנוע צורך להסתכל דרך כל אלה. אנחנו רוצים לשנות את המצבים מקומיים. היית צריך לקרוא את כל הדבר הזה כדי להבין איפה הכל משייכת כי אנחנו רוצים לשנות. אבל זה חלק פנימי של מצבים מקומיים לאן אנחנו הולכים רוצים לשנות את זה. אז hacker_settings.cc_lmode הוא מה זה נקרא. c_lflag. זה המקום בו אנו נכנסים למפעילים סיביים האופרטור. אנחנו סוג של מחוץ לזמן, אבל אנחנו עוברים את זה ממש מהר. זה המקום בו אנו נכנסים למפעילים סיביים האופרטור, שבו אני חושב שאמרתי פעם אחת לפני זמן רב כי בכל פעם שאתה מתחיל להתמודד עם דגלים, אתה הולך להיות באמצעות סיבי האופרטור מפעיל הרבה. כל ביט בדגל מתאים לסוג מסוים של התנהגות. אז הנה, את הדגל הזה יש חבורה של דברים שונים, שבו כולם מתכוונים למשהו אחר. אבל מה שאני רוצה לעשות הוא פשוט לכבות את החלק שמתאים לECHO. אז כדי להפוך את זה שאני עושה את & = ¬ ECHO. למעשה, אני חושב שזה כמו tECHO או משהו. אני רק הולך לבדוק שוב. אני יכול termios. זה פשוט ECHO. ECHO הולך להיות קצת בודד. ¬ ECHO הולך אומר כל הביטים מוגדרים 1, מה שאומר שכל הדגלים מוגדרים נכון פרט לקצת ההד. על ידי סיום הדגלים המקומיים שלי עם זה, זה אומר שכל הדגלים שכרגע מוגדרים נכון עדיין יהיה מוגדר נכון. אם דגל ECHO מוגדר נכון, אז זה מוגדר בהכרח כוזב על דגל ECHO. אז שורת קוד הזאת פשוט מכבה את דגל ECHO. בשורות האחרות בקוד, אני פשוט להעתיק אותם בעניין של זמן ואז להסביר אותם. בפתרון, הוא אמר 0. זה בטח יותר טוב לומר במפורש stdin. שים לב שאני גם עושה ECHO | ICANON כאן. ICANON מתייחס למשהו נפרד, מה שאומר שמצב קנונים. מה זה אומר מצב קנונים הוא בדרך כלל כשאתה מקליד את שורת הפקודה, סטנדרטי באינו מעבד כל דבר עד שתגיעי לשורה חדשה. אז מתי אתה GetString, אתה מקליד חבורה של דברים, אז אתה מכה שורה חדשה. זה, כאשר הם נשלחים לסטנדרטיים פנימה זה ברירת המחדל. כשאני מכבה את מצב קנונים, עכשיו כל תו בודד שתלחץ מה שגורם לעיבוד, שהיא בדרך כלל סוג של רע, כי זה איטי לעבד את הדברים האלה, וזה למה זה טוב להצפתו לקווים שלמים. אבל אני רוצה שכל תו להיות מעובד מאז אני לא רוצה אותו שיחכה לי להכות שורה חדשה לפני שהוא מעבד את כל הדמויות שאני מקליד. זה מבטל את מצב קנונים. החומר הזה פשוט אומר כאשר הוא בעצם מעבד תווים. משמעות הדבר הוא לעבד אותם באופן מיידי, ברגע שאני מקליד אותם, לעבד אותם. וזה התפקיד שהוא מעדכן את ההגדרות שלי לסטנדרט ב, ואמצעי TCSA לעשות את זה עכשיו. האפשרויות האחרות הן לחכות עד שכל מה שהוא כיום בזרם המעובד. זה לא ממש משנה. רק עכשיו לשנות את ההגדרות שלי להיות כל מה שנמצא כרגע בhacker_typer_settings. אני מניח שקראתי לזה hacker_settings, אז בואו נשנה את זה. לשנות הכל כדי hacker_settings. עכשיו בסוף התכנית שלנו שאנחנו הולכים ברצונך לחזור למה עכשיו בתוך normal_settings, שהוא הולך רק כדי להיראות כמו & normal_settings. שים לב שלא השתניתי כל normal_settings מאז מקבל אותו במקור. אז רק כדי לשנות אותם בחזרה, אני עובר אותם בחזרה בסוף. זה היה העדכון. אוקיי. עכשיו בתוך מכאן אני פשוט להסביר את הקוד בעניין של זמן. זה לא כל כך הרבה קוד. אנו רואים אנו קוראים תווים מהקובץ. אנחנו קראנו לזה f. עכשיו אתה יכול הגבר fgetc, אבל איך fgetc הוא הולך לעבוד הוא פשוט שזה הולך לחזור הדמות שאתה פשוט לקרוא או EOF, אשר מתאים לסוף הקובץ או התרחשות כלשהי שגיאה. אנו מתעקלים, ממשיכים לקרוא תו בודד מהקובץ, עד שנגמרנו לנו תווים לקריאה. ואם אנחנו כבר עושים את זה, אנחנו מחכים בתו בודד מתקן פנימה בכל פעם שאתה מקליד משהו בשורת הפקודה, שקוראים בתווי תקן מפנימה אז putchar רק הולך לשים char אנו קוראים מכאן לקובץ פלט סטנדרטי. אתה יכול אדם putchar, אבל זה רק עושה לסטנדרטי החוצה, זה שמדפיס אופי. אתה גם יכול לעשות printf ("% c", ג); אותו רעיון. זה הולך לעשות את חלק הארי של העבודה שלנו. הדבר האחרון שאנחנו הולכים רוצים לעשות הוא פשוט fclose הקובץ שלנו. אם אינך fclose, זה דליפת זיכרון. אנחנו רוצים fclose הקובץ שנפתחנו במקור, ואני חושב שזה זה. אם אנחנו עושים את זה, כבר יש לי בעיות. בואו נראים. מה זה להתלונן? צפוי "int", אבל טענה הוא מסוג 'struct _IO_FILE *'. נצטרך לראות אם זה עובד. מותר אך ורק בC99. Augh. אוקיי, תעשה hacker_typer. עכשיו אנחנו מקבלים הסבר שימושי יותר. אז שימוש במזהה לא מוצהרת "normal_settings '. אני לא קורא לזה normal_settings. אני קראתי לזה current_settings. אז בואו לשנות את כל זה. כעת עובר ויכוח. אני אעשה 0 זה לעת עתה. אוקיי. . / Hacker_typer cp.c. אני גם לא לנקות את המסך בהתחלה. אבל אתה יכול להסתכל אחורה לסדרת הבעיה האחרונה כדי לראות איך לנקות את המסך. זה פשוט מדפיס כמה תווים בזמן הזה היא עושה את מה שאני רוצה לעשות. אוקיי. וחשבתי למה זה צריך להיות 0 במקום stdin, שיש להגדיר # 0, זה מתלונן ש-- לפני כשאמרתי שיש מתארי קובץ אבל אז יש לך גם * הקובץ שלך, מתאר קובץ הוא רק מספר שלם אחד, בעוד * קובץ יש חבורה שלמה של דברים הקשורים אליו. הסיבה שאנחנו צריכים לומר 0 במקום stdin הוא stdin שהוא * קובץ שמצביע על הדבר שהוא התייחסות מתארת ​​קובץ 0. אז גם כאן כאשר אני עושה fopen (argv [1], אני מקבל * קובץ בחזרה. אבל אי שם שב* הקובץ הוא דבר מתאים למתאר הקובץ לקובץ זה. אם אתה מסתכל על הדף פתוח לאדם, ולכן אני חושב שאתה צריך לעשות אדם 3 פתוחים - nope - גבר 2 פתוחים - כן. אם אתה מסתכל על הדף לפתוח, פתוח הוא כמו fopen ברמה נמוכה יותר, וזה חוזר מתאר קובץ בפועל. fopen עושה כל מיני דברים בראש הפתוח, שבמקום לחזור רק שמתאר קובץ מחזיר קובץ שלם * מצביע בתוכה הוא מתאר הקובץ הקטן שלנו. אז סטנדרטי במתייחס לדבר * הקובץ, ואילו 0 מתייחסים רק סטנדרטי מתאר קובץ בעצמו. שאלות? [צוחק] השתולל. בסדר. אנחנו נעשינו. [צוחק] [CS50.TV]