C (שפת תכנות)

היסטוריה

שפת C תוכננה ופותחה לראשונה בין שנת 1969 לראשית שנות ה-70 (עד 1973). השפה נוצרה על ידי דניס ריצ'י, במעבדות חברת בל על מערכות ההפעלה UNIX שרצו על מחשבי PDP-11. מהר מאוד היא התפשטה ויצאה לאור ויצאו מספר גרסאות שונות לשפה אשר נבדלו בהבדלים קטנים זו מזו. הצורך בשפה עלה לשם כתיבת מערכת הפעלה. באותה עת הייתה קיימת שפת B, ובאמצעותה נכתבה שפת C בתחילה.
מקור השם של השפה הוא באותה שפת B, תוך התקדמות אות אחת באלפבית, או האות הבאה אחרי B בשמה של BCPL.

מאפיינים

שפת C היא שפה פרוצדורלית, בעלת טיפוסיות סטטית, חלשה, ולא בטוחה.
עיקרון מנחה בשפה הוא עקרון היעילות, ובכך היא מצטיינת. כל פעולה בשפה מתבטאת בפעולה בודדת או במספר מועט של פעולות בשפת סף. משום כך אין בה סוגי מבנים מורכבים כגון מחרוזות או בדיקות סמויות בגישה למערכים ומערכת הטיפוסים שלה לא בטוחה. מסיבה זו היא גם כוללת לא מעט אפשרויות לשגיאות שהמהדר (Compiler) לא יאתר, כגון פנייה לאיבר שלא קיים במערך או פנייה לערך לא חוקי בזיכרון וכדומה. הנחת העבודה של מתכנני השפה (והתקנים המאוחרים יותר) היא כי "המתכנת מבין מה הוא עושה".
סביבות עבודה לשפת C כיום כוללות מהדר, כלי לניפוי שגיאות (מציאת תקלות, באגים), וכן אפשרות לערוך את הקוד בחבילת תוכנה אחת.
שפת C היא שפה ה"מחזיקה את עצמה" (self hosting). כלומר, קיימים מהדרים לשפת C הכתובים אף הם בשפת C (לכך ניתן להגיע בתהליך הנקרא Bootstrapping).

התנהגות לא מוגדרת

בשפת C ישנם מצבים רבים שאינם מוגדרים בשפה, לרוב באופן מפורש. בין אלה ניתן למנות: ניסיון לשנות תוכן של ביטוי-מחרוזת, תוצאה של חלוקה באפס, הצבעה אל אזור מחוץ למערך (הרחוק יותר מאיבר אחד אחרי האיבר האחרון), שימוש במשתנה שאיננו מאותחל, ושינוי כפול של משתנה בביטוי בודד (למשל x=++x). במקרים אלה, המהדר רשאי לפעול כרצונו - להדר את התוכנית כולה בכל דרך שנראית לו, אף אם אין לה כל קשר לכוונה של המתכנת שכתב את הקוד, ואף אין דרישה לעקביות בהידור. כל זאת, במטרה לאפשר לכתוב למהדרים לקמפל תוכניות מוגדרות היטב בצורה יעילה, בכל סוגי המערכות.
ישנם מקרים המוגדרים לפי-מימוש. במקרים אלה, המהדר רשאי לפעול כאוות נפשו, אך יש לתעד את דרך הפעולה שנבחרה. מקרים אחרים מוגדרים בשפה, אך באופן חלש: סדר החישוב של ביטויים בקריאה לפונקציה איננו מוגדר, אך כל הביטויים צריכים להיות מחושבים לפני ביצוע הקריאה עצמה.

טיפוסי נתונים

טיפוסי הנתונים הבסיסיים בשפה הם מספרים שלמים (הנקראים char, short, int, long, long long לפי גודלם) וכן טיפוסים בעלי ייצוג נקודה צפה עבור מספרים ממשיים (float, double, long double).

מצביע

מצביע הוא משתנה המכיל כתובת בזיכרון המחשב - כתובת של משתנה או של פונקציה. משמש בין היתר ליצירה של מבני נתונים דינמיים.
לדוגמה, מצביע למשתנה מסוג short, שהוא משתנה המייצג מספר שלם:
short *x;

טיפוס בוליאני

בתקן ANSI לא קיים טיפוס נתונים בוליאני (המכיל ערכי אמת\שקר). אך  כל משתנה שלם עם ערך 0 נחשב כשקר, וכל ערך אחר היה אמת. בתקן C99 נוסף טיפוס בוליאני מיוחד הקרוי _Bool. במקרה ואין בעיות תאימות לקוד ישן, ניתן להשתמש בספרייה <stdbool.h> על מנת להשתמש בשם המקובל bool.

מערך

מערך בשפת C הוא אוסף של משתנים מאותו טיפוס המסודרים ברצף בזיכרון. ניתן לרוב להתייחס למערך כאילו הוא מצביע קבוע אל האיבר הראשון שלו. כך פונקציה הצריכה מערך כפרמטר, תבקש מצביע; אם יועבר אליה שם של מערך, הוא "ידעך למצביע" וכך יועבר כפרמטר. מספרו הסידורי של התא הראשון במערך בשפת C הוא 0, ולכן האיבר האחרון במערך a בן חמישה איברים יהיה [a[4‏ (גישה לאיבר מתבצעת על ידי רישום מספרו בתוך סוגריים מרובעים. [a[4‏ שקול בשפה ל(a + 4)*, כאשר a יכול להיות מצביע או מערך).
דוגמה להגדרת מערך של מספרים מסוג נקודה צפה (float):
float n[5];
לחלופין, ניתן להגדיר מערך על ידי אתחול ערכיו. נגדיר מערך של מספרים מסוג double, המייצג מספר נקודה צפה בדיוק כפול:
double n[] = {3.14, 2.71, 0.8, 5, 32};

מחרוזת

בשפת C עצמה אין הגדרה של מחרוזת. השפה מאפשרת לכתוב ביטויים בצורה "some string", אך אלה מתורגמים למערך של char, המסתיים בערך 0.
שלוש השורות הבאות שקולות לחלוטין מבחינת השפה:
char name[] = "John";char name[] = {'J', 'o', 'h', 'n', '\0'};char name[] = {'J', 'o', 'h', 'n', 0};
מבחינת הספרייה הסטנדרטית של השפה, מחרוזת היא כל מערך של char, המסתיים בערך אפס (שניתן לכתוב אותו ישירות בקוד כ-'0\'). בספרייה זאת מוגדרות פונקציות המטפלות במחרוזות - מוצאות את אורכן, מוצאות תו כלשהו או תת-מחרוזת בתוכן, וכן פעולות נוספות.

רשומה

רשומה (מכונה גם מבנה) היא טיפוס המגדיר N-יה סדורה של משתנים, שניתן להגדירם בבת אחת, כאשר כל אחד מהמשתנים הוא עצמאי. בניגוד למערך, לא כל המשתנים צריכים להיות מאותו הסוג. רשומה מאפשרת לאחד מספר משתנים העוסקים באותו פריט מידע לתוך משתנה יחיד.
דוגמה לרשומה (או מבנה) המתארת שם וגיל של אדם, המכילה שני שדות:
struct person {
 char *name;
 int age;};struct person yossi;
השפה מאפשרת לתת שמות חדשים לטיפוסים באמצעות המילה typedef. למשל:
typedef struct person tPerson;tPerson moshe;
מגדיר משתנה בשם moshe מהסוג struct person.

מבני בקרה

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

פקודת תנאי

מבנה הבקרה הפשוט והנפוץ ביותר הוא פקודת if. במבנה זה נבדק ערכו של תנאי מסוים, ומתבצע סט של פקודות אם התנאי נכון (ערך האמת שלו חיובי):
if (x < 5)
 printf("three is lower than five");
אם מתכוונים לבצע יותר מפקודה אחת כאשר התנאי מתקיים, יש להשתמש בסוגריים מסולסלים:
if (number_of_apples < 10) {
 printf("I am hungry!");
 printf("Give me more apples!");}
לפקודת תנאי ישנה גרסה מורחבת, המאפשרת להגדיר רצף של פקודות שיתבצע במקרה שהתנאי איננו מתקיים. לשם כך משתמשים במילה השמורה else ("אחרת", באנגלית):
if (number_of_apples < 10)
 printf("I am hungry");else
 printf("I am not hungry!");

לולאות

פקודת תנאי מאפשרת להגדיר קטע קוד שיבוצע פעם אחת, או לא יבוצע כלל. לעיתים קרובות יש צורך להגדיר קטע קוד שיבוצע מספר כלשהו של פעמים - פעם אחת, עשרים, או בכלל לא. לצורך כך משתמשים בלולאות.
ב־C קיימים שלושה סוגי לולאות: לולאות while שמבוססות על בדיקת תנאי להמשך הלולאה לפני ביצוע הקוד, לולאות do-while שדומות ללולאות while, אך התנאי נבדק בסוף הלולאה, ולולאות for, שבנוסף לבדיקת התנאי גם משלבות בצורה טבעית משתני עזר שיפקחו על ריצת הלולאה (למשל, ספירת מספר הפעמים שהלולאה חזרה על עצמה).
לולאת for:
for (int i = 0 ; i < 5 ; i++) {
 /* הכנס כאן קוד... */}
לולאת while עם בקרה בכניסה:
while (/* הכנס כאן תנאי */) {
 /* הכנס כאן קוד... */ 
}
לולאת do-while עם בקרה ביציאה:
do {
 /* הכנס כאן קוד... */} while (/* הכנס כאן תנאי */);

פונקציות

פונקציה היא המבנה הבסיסי של השפה שבו יכולות להתבצע פקודות ומבני בקרה. אוסף של פקודות שיכול (אך לא חייב) לקבל אוסף של ערכים כקלט (ערכים אלה, והמשתנים המחזיקים אותם, נקראים "פרמטרים" או "ארגומנטים"), ולהחזיר ערך יחיד כפלט (ערך זה נקרא "ערך החזרה"). שינוי בערכי משתני הקלט בפונקציה לא ישפיע על ערכיהם בחלקים אחרים בתוכנית, שכן הם מועברים תמיד על פי ערך (by value).
דוגמה לפונקציה המקבלת שני מספרים ומחזירה את ערכו של הגדול מביניהם:
int max(int a, int b){
 if (a > b)
 return a;
 else
 return b;}
בהינתן הגדרה של פונקציה כזאת, ניתן להשתמש בה על מנת לקבל את המקסימום מבין שני מספרים בצורה קלה. למשל, הפקודה הבאה:
printf("%d", max(5, 3));
תדפיס את המספר 5, כי הוא המספר הגבוה יותר מבין המספרים 3 ו-5.
בשונה מפונקציות מתמטיות, פונקציות בשפת C יכולות לבצע פעולות שמשנות את המצב של המחשב. לדוגמה, הפונקציה printf מהדוגמה הקודמת היא פונקציה שמבצעת הדפסה למסך (או לאמצעי פלט אחר).

מודולים

מודול בשפת C הוא יחידה עצמאית של תוכנה, המהודרת אל קובץ עצמאי. קובץ כזה לא ניתן להריץ באופן ישיר, אך ניתן להשתמש בו בקבצים אחרים. קובצי h. שמכילים הגדרות של מודולים מאפשרים למהדר להבין מתי משתמשים בתוכן של מודול חיצוני ומתי משתמשים בתוכן של הקובץ הנוכחי. בכל תוכנה יש מודול אחד ראשי שמכיל את הפונקציה main שבה מתחילה התוכנית.
אוסף של פקודות המאוגדות יחד במקום אחד המאפשר לנו להשתמש בהן ללא צורך לכתיבתן מחדש נקרא ספרייה. בשפת C יש צורך לבצע פקודת include# לקובץ שמכיל את ההגדרה של הספרייה בשביל להשתמש בה.
לדוגמה, הכללת הספרייה string, המכילה פונקציות לטיפול במחרוזות:
#include <string.h>
לפני תרגום התוכנית לשפת מכונה, תוכנית הנקראת קדם-מעבד (pre-processor) יוצרת קובץ חדש הכולל בנקודה בה מוכרזת ההכללה את תוכן הקובץ המוכלל, ואז מועבר הקובץ להידור לשפת מכונה.
שפת C היא שפה קטנה למדי. היא אינה מכילה פקודות הקושרות אותה למערכת הפעלה מסוימת או לתחום תוכנה מסוים. מסיבה זו, פקודות ספציפיות שכאלה (למשל לקלט ופלט, לחישובים מתמטיים, לעבודה עם מחרוזות ולהקצאות זיכרון דינמיות) נמצאות בספריות סטנדרטיות, שאינן חלק מתחביר השפה אך עונות לקונבנציה מוכרת וקבועה ומהוות חלק מהתקן הרשמי שלה. ספריות אלה מסופקות עם המהדר עבור מערכת הפעלה וחומרה מסוימות והתוכנה מקושרת (linked) עימן על פי צורך והגדרת כותב הקוד.

"Hello World"

תוכנית פשוטה זו מדפיסה את המחרוזת "Hello, World!‎" לפלט הסטנדרטי.
#include <stdio.h>int main(void){
printf("Hello, World!\n");
return 0;}