Menu

איך לקמפל ולהריץ תוכנית C (gcc, צעד אחר צעד)

הפכו קובץ .c לתוכנית שרצה: הפקודה gcc, מה באמת עושים שלבי הקדם-עיבוד, הקומפילציה והקישור, הדגלים שכדאי להשתמש בהם מהיום הראשון, ואיך לקרוא את השגיאות כשמשהו נכשל.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

C היא שפה מקומפלת: שום דבר לא רץ עד שקומפיילר תרגם את קוד המקור שלכם לקוד מכונה עבור המעבד ומערכת ההפעלה הספציפיים שלכם. השלב הזה הוא פקודה אחת, אבל הבנה של מה שהוא עושה הופכת את רוב הודעות השגיאה ממסתוריות לברורות.

שתי הפקודות

שמרו את זה בשם hello.c:

#include <stdio.h>

int main(void) {
    printf("Hello, C!\n");
    return 0;
}

ואז, באותה תיקייה:

gcc hello.c -o hello
./hello

הפקודה הראשונה מקמפלת. השנייה מריצה את התוצאה. ב-Windows פקודת ההרצה היא hello ב-Command Prompt או .\hello ב-PowerShell: אין קידומת ./, כי Windows מחפשת בתיקייה הנוכחית כברירת מחדל ו-shells של Unix בכוונה לא.

אם משמיטים את -o hello, gcc קורא לפלט a.out (או a.exe), ולכן מדריכים ישנים מסתיימים ב-./a.out. תמיד תנו שם לפלט; זה עולה ארבעה תווים וחוסך בלבול.

הנה אותה תוכנית בעורך שבדפדפן, שעושה את שני השלבים בשבילכם:

מה "קומפילציה" עושה בפועל

gcc hello.c -o hello נראה כמו שלב אחד. אלה ארבעה, וכל אחד מהם יכול להיכשל עם סוג שגיאה משלו.

1. קדם-עיבוד. לפני שקוד C כלשהו מקומפל, קדם-המעבד מטפל בכל שורה שמתחילה ב-#. #include <stdio.h> מוחלף תרתי משמע בתוכן של קובץ הכותרת הזה; מאקרו של #define מורחבים; בלוקים של #ifdef נשמרים או נמחקים. הפלט הוא קובץ C אחד גדול בלי אף שורת #. אפשר לראות אותו:

gcc -E hello.c

זה מדפיס מאות שורות, וכמעט כולן הן stdio.h שהודבק פנימה.

2. קומפילציה. ה-C שעבר קדם-עיבוד מנותח, נבדק מבחינת טיפוסים ומתורגם לאסמבלי עבור המעבד שלכם. מכאן מגיעות שגיאות תחביר, שגיאות טיפוסים ואזהרות.

3. אסמבלי. האסמבלי הופך לקובץ אובייקט: קוד מכונה עם מקומות שמורים במקום שבו הולכות הקריאות לפונקציות מקבצים אחרים.

gcc -c hello.c   # produces hello.o, stops before linking

4. קישור. קובצי האובייקט נתפרים יחד עם הספרייה הסטנדרטית של C, כל מקום שמור מתמלא בכתובת אמיתית, והתוצאה היא קובץ הרצה. מכאן מגיעות שגיאות "undefined reference".

המסקנה המעשית: שגיאת קומפילציה מצביעה על שורה בקוד המקור שלכם, ושגיאת קישור לא, כי הקישור קורה אחרי שכל השורות כבר התקבלו.

דגלים שכדאי להשתמש בהם מהיום הראשון

gcc file.c -o prog החשוף מקבל בשקט הרבה קוד מסוכן. ארבעה דגלים משנים את זה.

gcc -std=c17 -Wall -Wextra -g hello.c -o hello
  • -Wall מפעיל את האזהרות הנפוצות. למרות השם, אלה לא "כל" האזהרות: זו הקבוצה ההגיונית.
  • -Wextra מוסיף עוד, כולל פרמטרים שלא בשימוש וכמה טעויות השוואה.
  • -std=c17 מקבע את תקן השפה כדי שלקוד שלכם תהיה אותה משמעות בכל מכונה. השתמשו ב--std=c99 אם אתם עוקבים אחרי חומר ישן יותר.
  • -g שומר מידע דיבאג, כך ש-gdb או lldb יכולים להציג את שורות המקור האמיתיות שלכם כשמשהו קורס.

עוד שניים שתרצו בשלב מסוים:

  • -O2 מפעיל אופטימיזציה לגרסאות release. השאירו אותו כבוי בזמן הלמידה: קוד עם אופטימיזציה קשה יותר לדיבאג, ואזהרות לפעמים משתנות.
  • -fsanitize=address,undefined (gcc ו-clang) גורם לתוכנית לעצור עם הודעה ברורה ברגע שהיא קוראת מחוץ לגבולות או נתקלת בהתנהגות לא מוגדרת. זה הדגל השימושי ביותר ללמידת C.

נסו את האזהרות בעצמכם. התוכנית הזו מתקמפלת ורצה, אבל יש בה שני באגים אמיתיים:

count אף פעם לא מקבל ערך, כך שהתוכנית מדפיסה את הבתים שבמקרה היו שם. עם -Wall הקומפיילר אומר את זה: 'count' is used uninitialized. אזהרות ב-C הן כמעט אף פעם לא רעש: התייחסו אליהן כאל שגיאות שעוד לא נתקלתם בהן.

קומפילציה של יותר מקובץ אחד

תוכניות אמיתיות מפוצלות לכמה קבצים. העבירו את כולם ל-gcc:

gcc -std=c17 -Wall main.c utils.c -o myprog

או קמפלו כל אחד בנפרד וקשרו בסוף, וזה מה שמערכות בנייה עושות כדי ששינוי של קובץ אחד לא יבנה מחדש הכול:

gcc -c main.c      # -> main.o
gcc -c utils.c     # -> utils.o
gcc main.o utils.o -o myprog

ספריות מסוימות צריכות דגל קישור מפורש. ספריית המתמטיקה היא זו שכל מתחיל פוגש:

gcc calc.c -o calc -lm

בלי -lm, שימוש ב-sqrt מ-math.h מתקמפל בלי בעיה ואז נכשל בשלב הקישור עם undefined reference to sqrt: ההצהרה הייתה בקובץ הכותרת, אבל הקוד היה בספרייה שאף אחד לא ביקש.

קריאת השגיאות

השגיאות של C תמציתיות אבל עקביות. שלוש דוגמאות מכסות את רוב מה שתראו בהתחלה.

נקודה ופסיק חסרה מדווחת על השורה הבאה, כי הקומפיילר המשיך לקרוא:

hello.c:5:5: error: expected ';' before 'return'

התיקון שייך לשורה 4, לא לשורה 5. בכל פעם ששגיאה מצביעה על שורה שנראית תקינה, בדקו את השורה שלפניה.

קובץ כותרת חסר נראה כמו תעלומה סביב פונקציה שברור שכתבתם נכון:

hello.c:4:5: warning: implicit declaration of function 'printf'

זה אומר שהקומפיילר מעולם לא ראה הצהרה על printf, כלומר שכחתם #include <stdio.h>. מ-C99 ואילך זו שגיאה, לא רק אזהרה.

כישלון קישור לא כולל מספר שורה בכלל:

/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x1a): undefined reference to `helper'

הקומפיילר האמין לכם ש-helper קיימת איפשהו; ה-linker חיפש והיא לא קיימת. או שמעולם לא כתבתם את ההגדרה, או שכתבתם את השם אחרת, או ששכחתם להעביר ל-gcc את הקובץ שמכיל אותה.

תמיד תקנו את השגיאה הראשונה. שגיאות C מתגלגלות: שורה גרועה אחת יכולה לייצר עשרים הודעות, ותשע-עשרה מהן נעלמות כשמתקנים את הראשונה.

קודי יציאה

return 0 מ-main אינו קישוט. זה קוד היציאה של התוכנית, וה-shell יכול לקרוא אותו:

אחרי ההרצה, echo $? ב-macOS/Linux (או echo %errorlevel% ב-Windows) מדפיס את המספר הזה. סקריפטים וכלי בנייה משתמשים בו כדי להחליט אם להמשיך. המוסכמה מוחלטת: 0 הוא הצלחה, וכל ערך שונה מאפס הוא קוד שגיאה שאתם בוחרים. stdlib.h מגדיר את EXIT_SUCCESS ואת EXIT_FAILURE אם אתם מעדיפים את השמות.

מאז C99, הגעה לסוף של main בלי return נחשבת כ-return 0, אבל כתיבה מפורשת ברורה יותר, והיא חובה בכל פונקציה אחרת.

שאלות נפוצות

איך מקמפלים ומריצים תוכנית C?

שמרו את הקוד בשם hello.c, ואז הריצו gcc hello.c -o hello כדי לבנות אותו ו-./hello כדי להריץ אותו (רק hello ב-Windows). אם gcc לא נמצא, צריך קודם להתקין קומפיילר.

מה עושה gcc -o?

-o נותן שם לקובץ הפלט. gcc hello.c -o hello מפיק קובץ הרצה בשם hello. בלי -o, gcc כותב ל-a.out (או ל-a.exe ב-Windows), ולכן כל כך הרבה מדריכים ישנים מריצים ./a.out.

באילו דגלים של gcc כדאי להשתמש תמיד?

gcc -std=c17 -Wall -Wextra -g yourfile.c -o yourprog. -Wall -Wextra מפעילים את האזהרות שתופסות באגים אמיתיים, -std=c17 מקבע את גרסת השפה, ו--g שומר סמלי דיבאג כדי שדיבאגר יוכל להציג את קוד המקור שלכם. הוסיפו -O2 כשרוצים מהירות בגרסת release.

מה פירוש "undefined reference to" ב-C?

זו שגיאת קישור (linker): הקומפיילר קיבל קריאה לפונקציה, אבל לא נמצאה הגדרה של הפונקציה הזו. סיבות נפוצות הן שגיאת כתיב בשם, שכחה לקמפל קובץ .c שני, או שימוש בפונקציה מתמטית בלי לקשר את ספריית המתמטיקה (-lm).

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל