void aFoo1()
Download
Report
Transcript void aFoo1()
הרצאה 05
חלוקת הפרויקט לקבצים
פקודות קדם מעבד והידור מותנה
קרן כליף
ביחידה זו נלמד:
חלוקת הפרויקט לקבצי headerוקבצי source
בעיות ב'include -ים כפולים
הידור מותנה
מאקרו
תהליך הקומפילציה
2
© Keren Kalif
יצירת קובץ ספריה
עד כה השתמשנו בפונקציות שקיבלנו משפת C
כל פונקציה כזו נכתבה בספריה שאליה ביצענו include
למשל כדי להשתמש בפונקציות של מחרוזות ,עשינו
>#include <string.h
כדי לייצר ספריה משלנו נייצר 2קבצים חדשים:
– <file_name>.h קובץ זה יכלול את ה prototypes -של
הפונקציות ,מבנים ו'include -ים.
לקובץ זה נעשה includeמהקובץ שירצה להשתמש בפונקציות המוגדרות בו
– <file_name>.c יכיל includeלקובץ ה header -התואם ויממש
את הפונקציות שבו
include לספריה שכתבנו יהיו בתוך "" (גרשיים) ולא בתוך <>
3
© Keren Kalif
)1( דוגמא – ספריה המטפלת בתווים
// prototypes
int isSmallLetter(char);
int isCapitalLetter(char);
int isAlpha(char);
int isDigit(char);
character.h הקובץ
© Keren Kalif
4
)2( דוגמא – ספריה המטפלת בתווים
#include "charachter.h"
int isSmallLetter(char ch)
{
return (ch >= 'a' && ch <= 'z');
}
int isCapitalLetter(char ch)
{
return (ch >= 'A' && ch <= 'Z');
}
int isAlpha(char ch)
{
return (isSmallLetter(ch) || isCapitalLetter(ch));
}
int isDigit(char ch)
{
return (ch >= '0' && ch <= '9');
}
character.c הקובץ
© Keren Kalif
5
)3( דוגמא – ספריה המטפלת בתווים
#include <stdio.h>
#include "charachter.h"
main.c הקובץ
void main()
{
char ch=0;
printf("Please enter a charachter: ");
ch = getchar();
printf("Is '%c' a digit? %d\n", ch, isDigit(ch));
printf("Is '%c' a small letter? %d\n", ch, isSmallLetter(ch));
printf("Is '%c' a capital letter? %d\n", ch, isCapitalLetter(ch));
printf("Is '%c' a letter? %d\n", ch, isAlpha(ch));
}
© Keren Kalif
6
ספריות שלנו -סיכום
ניתן לכתוב את כל הקוד בקובץ אחד ,אבל אם אנחנו כותבים קוד
כללי ,שיתכן ויהיה שימושי גם במקומו אחרים ,נעדיף לחלק את
הקוד לקובץ ספריה נפרד
מי שירצה להשתמש בספריה שלנו ,יתעניין מה יש לה להציע ,ולא
איך היא מבצעת את הפעולות ,וכך הוא יוכל להסתכל בקובץ
headerבלבד בו יש ריכוז של כל הפוקנציות
7
© Keren Kalif
include - פעולת ה:תזכורת
) אשרpreprocessor( מעבד- היא פקודת קדםinclude - פעולת ה
את תוכן הקובץ שאותוinclude שותלת בקוד במקום כל פקודת
כללנו בפקודה
a.h
// prototypes
void aFoo1();
int aFoo2();
main.c
#include <stdio.h>
#include "a.h"
void main()
}
aFoo1();
aFoo2();
{
#include <stdio.h>
// prototypes
void aFoo1();
int aFoo2();
void main()
}
aFoo1();
aFoo2();
{
© Keren Kalif
8
a.h
// prototypes
void aFoo1();
int aFoo2();
include הבעיתיות בפקודת
: לקובץ מסוים יותר מפעם אחתinclude יתכן ונעשה
b.h
#include “a.h”
// prototypes
void bGoo1();
int bGoo2();
main.c
#include <stdio.h>
#include "a.h“
#include “b.h“
void main()
}
aFoo1();
bGoo1();
{
#include <stdio.h>
// prototypes
void aFoo1();
int aFoo2();
// prototypes
void aFoo1();
int aFoo2();
// prototypes
Void bGoo1();
int bGoo2();
נקבל שגיאה של
redefinition
מאחר והקומפיילר
רואה את ההצהרה
על הפונקציות
a.h -שמוגדרות ב
יותר מפעם אחת
void main()
}
aFoo1();
bGoo1();
{
© Keren Kalif
9
הפתרון :הידור מותנה
ראינו בעבר את הפקודה #defineלצורך הגדרת קבוע מסוים
פקודה זו מוסיפה את הקבוע שהוגדר לטבלת סימולים של
התוכנית במידה וטרם הוגדר .במידה וכבר הוגדר דורסת את
ערכו.
ניתן גם לכתוב פקודת defineללא ערך ,רק כדי להכניס קבוע
מסוים לטבלת הסימולים
ניתן לבדוק האם קבוע מסוים הוגדר בטבלת הסימולים בעזרת
הפקודה #ifdefאו אם לא הוגדר בעזרת הפקודה #ifndef
במידה והתנאי מתקיים ,הקומפיילר יהדר את קטע הקוד
הבא עד אשר יתקל ב#endif -
10
© Keren Kalif
a.h
#ifndef __A_H
#define __A_H
// prototypes
void aFoo1();
int aFoo2();
#endif // __A_H
הפתרון עם הידור מותנה
b.h
#ifndef __B_H
#define __B_H
#include “a.h”
// prototypes
void bGoo1();
int bGoo2();
#endif // __B_H
main.c
#include <stdio.h>
#include "a.h“
#include “b.h“
void main()
}
aFoo1();
bGoo1();
{
:טבלת הסימולים
__A_H
__B_H
לאחרmain.c
preprocessor
#include <stdio.h>
// prototypes
void aFoo1();
int aFoo2();
// prototypes
void bGoo1();
int bGoo2();
void main()
}
aFoo1();
bGoo1();
{
main -כעת יש לנו ב
פעם אחת בלבד את
ההגדרות מכל קובץ
© Keren Kalif
11
מאקרו
, כדי לבצע פעולה מסוימתdefine ניתן להשתמש בפקודת
ולא רק לצורך הגדרת קבועים
מספרים2 הגדרת מאקרו המוצא מקסימום בין: דוגמא
#include <stdio.h>
#include <stdio.h>
#define max(a, b) a > b ? a : b
מה שהקומפיילר רואה
void main()
void main()
:precompile לאחר
{
{
printf("max is %d\n", 13 > 5 ? 13 : 5);
printf("max is %d\n", max(13, 5));
printf("max is %d\n", 5 > 13 ? 5 : 13);
printf("max is %d\n", max(5, 13));
{
{
© Keren Kalif
12
מאקרו -דגשים
בין שם המאקרו לסוגריים של הפרמטרים אסור שיהיה רווח,
אחרת מתקבלת שגיאת קומפילציה שלא ממש עוזרת להבנת
הבעיה
#define max(a, b) a > b ? a : b
בכתיבת המאקרו מאוד מומלץ בשימוש לעטוף כל פרמטר ב:)( -
)(void main
}
;))printf("sum is %d\n", (2+4) * (6/2
;))printf("sum is %d\n", 2+4 * 6 / 2
{
13
© Keren Kalif
)#define mult1(x, y) (x) * (y
#define mult2(x, y) x * y
מה שהקומפיילר רואה
לאחר :precompile
)(void main
}
;))printf("sum is %d\n", mult1(2+4, 6/2
;))printf("sum is %d\n", mult2(2+4, 6/2
{
מאקרו ושימוש באופרטור #
כאשר בתוך מאקרו שמים #לפני שם הפרמטר ,הקומפיילר עוטף
אותו בגרשיים ולכן מתייחס לשמו ולא לערכו
הערת סוגריים :הפקודה הבאה תקינה:
;)"printf("hi" " hello\n
דוגמא:
;)"#define PRINT(str) printf(#str "\n
)(main
}
;)"printf)“hello world” "\n
{
14
© Keren Kalif
)(main
}
;)PRINT(hello world
{
דוגמא- # מאקרו ושימוש באופרטור
#define PRINT_INT_VALUE(x)
printf(#x " = %d\n", x);
#define PRINT_VALUE(x, modifier) printf(#x " = %" #modifier "\n", x);
void main()
}
int value = 123;
char ch = 'a';
void main()
}
int value = 123;
char ch = 'a';
PRINT_INT_VALUE(value);
PRINT_VALUE(value, d);
PRINT_VALUE(ch, c);
PRINT_VALUE(ch, d);
{
printf("value" " = %d\n", value);
printf("value" " = %" "d" "\n", value);
printf("ch" " = %" "c" "\n", ch);
printf("ch" " = %" "d" "\n", ch);
{
© Keren Kalif
15
ההבדל בין מאקרו לפונקציה
ראינו כי ניתן לממש פונקציות בעזרת מאקרו
הבדלים בין פונקציה למאקרו :
פיענוח המאקרו קורה בזמן קומפילציה (בשלב ה)preprocessor -
בעוד שקריאה לפונקציה קוראת בזמן ריצה ,ויש לקפוץ למיקום
הפונקציה בזיכרון
שימוש במאקרו מנפח את ה EXE -מאחר והוא נפרש ומשוכפל בכל
קריאה ,בניגוד לפונקציה
לשימוש במאקרו יש חיסרון שלא ניתן לדבג אותו בעזרת ,F10וכן
הוא יותר קשה להבנה ,כאשר הוא כולל הרבה פעולות
16
© Keren Kalif
דוגמא לשימוש בהידור מותנה ובמאקרו
נרצה לכתוב תוכנית המכילה הדפסות לצרכי debug
נרצה לאפשר כמה סוגים של הדפסות .למשל:
הדפסת ההודעה בציון שם הקובץ והשורה מהם נובעת
ההודעה
הדפסת ההודעה ללא נתונים נוספים
לא להדפיס הודעות בכלל
לא נרצה לעשות 'ifים בקוד שלנו ,ולכן נשתמש בהידור מותנה
17
© Keren Kalif
#define DEBUG_WITH_DETAILS
דוגמא לשימוש
)1( בהידור מותנה
#ifdef DEBUG_SIMPLE
#define PRINT(str) printf("%s\n", str);
#else
#ifdef DEBUG_WITH_DETAILS
#define PRINT(str) printf("%s (%d): %s\n", __FILE__, __LINE__, str);
#else
#define PRINT(str) ;
#endif
#endif
__ הוא מאקרו קיים לשם הקובץ הנוכחיFILE__
void foo()
}
PRINT("--> foo");
PRINT("<-- foo");
{
void main()
}
PRINT("--> Main");
foo();
PRINT("<-- Main");
{
__ הוא מאקרו קיים לשורה הנוכחית בקובץLINE__
© Keren Kalif
18
#define DEBUG_SIMPLE
דוגמא לשימוש
)2( בהידור מותנה
#ifdef DEBUG_SIMPLE
#define PRINT(str) printf("%s\n", str);
#else
#ifdef DEBUG_WITH_DETAILS
#define PRINT(str) printf("%s (%d): %s\n", __FILE__, __LINE__, str);
#else
#define PRINT(str) ;
#endif
#endif
void foo()
}
PRINT("--> foo");
PRINT("<-- foo");
{
void main()
}
PRINT("--> Main");
foo();
PRINT("<-- Main");
{
© Keren Kalif
19
דוגמא לשימוש
)3( בהידור מותנה
#ifdef DEBUG_SIMPLE
#define PRINT(str) printf("%s\n", str);
#else
#ifdef DEBUG_WITH_DETAILS
#define PRINT(str) printf("%s (%d): %s\n", __FILE__, __LINE__, str);
#else
#define PRINT(str) ;
#endif
#endif
void foo()
}
PRINT("--> foo");
PRINT("<-- foo");
{
void main()
}
PRINT("--> Main");
foo();
PRINT("<-- Main");
{
© Keren Kalif
20
תהליך הקומפילציה
Disk
Editor
Disk
Preprocessor
Disk
Compiler
Disk
Linker
התוכנית נכתבת בעורך טקסטואלי ונכתבת לדיסק
קדם המעבד עובר על הקוד ומבצע החלפות נחוצות
(כל הפקודות המתחילות ב ,# -כמו defineוinclude -
עבור כל קובץ ,cהקומפילר יוצר קובץ objבשפת מכונה ושומר אותו
על הדיסק .בשלב זה רק נבדקת תקינות הסינטקס בפונקציות.
ה linker -קושר את קובץ ה obj -עם הספריות ,ויוצר קובץ exeהמוכן
להרצה ונשמר בדיסק .כלומר ,מקשר בין קריאה לפונקציה ,למימוש
שלה.
Primary Memory
Loader
..
..
..
Disk
Primary Memory
CPU
..
..
..
21
בעת ההרצה ,טוענים את התוכנית מהדיסק לזיכרון הראשי
© Keren Kalif
עם הרצת התוכנית ,ה cpu -עובר על כל פקודה ומריץ אותה
דוגמא לשגיאת קומפילציה
#include <stdio.h>
void foo(int x)
}
printf("the number is %d\n");
{
void main()
{
for (i=0 ; i < 5 ; i++)
}
printf("%d ", i);
goo(i);
{
}
:נקבל את שגיאת הקומפילציה הבאה
error C2065: 'i' : undeclared identifier
:goo הקומפיילר רק נותן אזהרה שהוא לא מוצא את
warning C4013: 'goo' undefined; assuming
extern returning int
הקומפיילר לא ממשיך לתהליך לינקר,במידה ויש שגיאות קומפילציה
© Keren Kalif
22
דוגמא לשגיאת לינקר
#include <stdio.h>
void foo(int x)
}
printf("the number is %d\n");
{
void main()
{
int i;
for (i=0 ; i < 5 ; i++)
}
printf("%d ", i);
goo(i);
{
תוכנית זו מתקמפלת (אין שגיאות סינטקטיות בתוך
,הפונקציות) ולכן הקומפיילר עובר לתהליך הלינקר
:ומוציא את השגיאה הבאה
error LNK2019: unresolved external symbol
_goo referenced in function _main
}
© Keren Kalif
23
קומפילציה ביוניקס
24
כדי לקמפל את כל הפרוייקט שלנו ב visual studio -לחצנו את
buildעבור הפרוייקט ,התוצר היה קובץ EXEבספרית DEBUG
כדי להעביר תהליך קומפילציה מלא הכולל לינקר בלינוקס:
]… gcc >file.c< [file.c
התוצר להרצה יהיה הקובץ out.a
אם רוצים ששם התוצר יהיה שונה:
>שם התוצר< gcc >file.c< [file.c …] –o
כדי לקבל גם warningsיש לקמפל עם הדגל -Wall
© Keren Kalif
קימפול ביוניקס :רק קדם-מעבד
לעיתים מקבלים שגיאות שקשה לאתר את הבעיה:
קימפול שמראה את שלב הביניים שאותו המחשב מקמפל ,היה
עוזר לפתור את הבעיה:
>gcc –E <file.c
התוצר של פקודה זו הוא רק פלט למסך
25
© Keren Kalif
קומפילציה ביוניקס :קומפילציה בלבד
פקודת gccמקמפלת ,ואם הקימפול עבר בהצלחה מבצעת גם את
שלב הלינקר
התוצר של שלב הקומפילציה בלבד הוא קובץ עם סיומת oעבור כל
קובץ cשקימפלנו
התוצר של הלינקר הוא איחוד כל קבצי ה obj -לכדי קובץ הרצה אחד
ניתן לקמפל את התוכנית ללא לינקר:
]…<gcc –c <file1.c> [<file2.c
בהמשך ניתן לקמפל רגיל את כל קבצי ה o -לכדי קובץ הרצה
היתרון :למשל כאשר אין לי גישה לקוד המקור וקיבלנו רק רכיבים
בודדים
כדי לראות את הפונקציות המוגדרות בקובץ :o
nm <fileName>.o
26
© Keren Kalif
עבודה עם ספריות מוכנות
27
בעולם האמיתי נשתמש בספריות מוכנות שהורדנו מהאינטרנט /
שקנינו מאישזהי חברה וכד'
ספריות כאלו לרוב לא יכילו את הקוד עצמו (כדי לא לחשוף את
האלגוריתם שלהם) ,אלא את הקוד מקודד באופן בינארי המוכן
לשימוש ,ולא לקריאה
לצורך ההקבלה :כאשר אנחנו קונים תוכנה מסויימת ,אנחנו
מקבלים רק את קובץ ההרצה שלה ,ולא את הקוד של איך
התוכנית כתובה
בניגוד לקבצי הרצה ,לא יהיה בתוך הספריה פונקציית main
אם קובץ עם סיומת oהוא תוצר קומפילציה בינארי של קובץ c
אחד ,אז ספריה הינה אוסף של כמה קבצים עם סיומת ( oלמעשה
סוג של מארז)
© Keren Kalif
קומפילציה ביוניקס :יצירת ספריה
כדי לייצר ספריה ביוניקס:
]… >.a <file1>.o [<file2<.oשם הספריה שנרצה<ar r lib
דוגמא:
ar r libfunctions.a functions1.o functions2.o
שם הספריה חייב להתחיל ב lib -ולהסתיים בa -
ללקוחות שלנו נשלח:
את התוצר (קובץ עם סיומת )aהמכיל באופן בינארי את כל מה שיש
בקבצי ה.o -
את קבצי ה h -המכילים את הגדרות הפונקציות (לצרכי קומפילציה)
כדי לראות את ה'object -ים המוכלים בספריה:
>ar t <lib
28
© Keren Kalif
קומפילציה ביוניקס :שימוש בספריה
כדי לקמפל את הקבצים שלנו עם קובץ ספרייה:
<hמיקום קבצי ה> -I< -שם הספריה<> –lמיקום הספריה<gcc <fileName>.c -L
דוגמא ,עבור קימפול הקובץ main.cעם הספריה libfamily.a
שנמצאת בתת הספריה ,familyLibשבה גם יש את קבצי ה:h-
gcc main.c -LfamilyLib -lfamily -IfamilyLib
הדגל -Lמציין את מקום הספריה
יהיה .אם הספריה נמצאת באותו מיקום בו אנו מקמפלים
הדגל -Iמציין את מיקום קבצי הh -
29
© Keren Kalif
קומפילציה ב :windows -יצירת lib
נייצר פרוייקט שאינו מייצר ,EXEאלא :LIB
30
© Keren Kalif
קומפילציה ב :windows -שימוש בlib -
נייצר פרוייקט רגיל ,ונשנה את הגדרותיו:
שקול לדגל -Iבקומפילציה ביוניקס
31
© Keren Kalif
קומפילציה ב :windows -שימוש בlib -
שקול לדגל -Lבקומפילציה ביוניקס
32
© Keren Kalif
()2
קומפילציה ב :windows -שימוש בlib -
שקול לדגל -lבקומפילציה ביוניקס
33
© Keren Kalif
()3
קישור דינאמי לספריה לעומת קישור סטטי
הקישור שעשינו לספריה הינו קישור סטטי
כלומר ,החלפת הספריה בספריה זהה ,שרק מימוש הפונקציות שונה,
תדרוש קומפילציה מחדש של הקובץ שהשתמש בספריה
ניתן לבצע קישור דינאמי
כלומר ,כל פעם שתהיה הרצה של התוכנית ויהיה שימוש בפונקציה
מהספריה ,הקומפיילר יחפש את המימוש בזמן ריצה
לכן ניתן להחליף את הספריה ללא לקמפל מחדש
שימו לב :החלפת הספריה משמע שהיא תכיל את אותן פונקציות
עם אותן חתימות בדיוק ,רק המימושים שונים!
34
© Keren Kalif
מוזמנים ללמוד לבד את הנושא
ביחידה זו למדנו:
חלוקת הפרויקט לקבצי headerוקבצי source
בעיות ב'include -ים כפולים
הידור מותנה
מאקרו
תהליך הקומפילציה
35
© Keren Kalif