לא מבין משהו במערך CHARים, בC - תכנות - HWzone פורומים
עבור לתוכן
  • צור חשבון

לא מבין משהו במערך CHARים, בC


SweeT_EviL

Recommended Posts

כיוויכול מערך CHARים אמור לייצג STRING וגודל המערך שלו אומר כמה תווים הSTRING יכיל, עד פה פסדר?

קיצר לקוד הבא:

char c[2];
scanf("%s", &c);
prinf("%s", c);

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

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

מה שמוביל אותי לשאלה שלי - איך הSTRING הולך בC? (זה אותו דבר גם בC++ אני מניח).

קישור לתוכן
שתף באתרים אחרים

  • תגובות 50
  • נוצר
  • תגובה אחרונה

1) אתה יכול לשמור במחרוזת שלך רק תו אחד, מכיוון שתמיד התו האחרון הוא \0.

2) כשאתה רושם char c[2], התאים של המערך מקבלים ערך זבל שלא ניתן לדעת מה הוא.

3) כפי שכבר נרשם, SCANF לא יודע מה גודל המחרוזת, ולכן הוא ישים בכתובת שיש בC את התוים שהכנסת + '\0' בסוף.

4) אם תכניס ABC, יתכן ותדרוס בטעות מקום אחר בזיכרון.

5) CHAR*, או מערך של CHAR(כפי שרשמת בקוד שלך) הוא מצביע למקום בזיכרון. הקומפיילר של C לא מזהה את זה בתור מחרוזת, ולא יודע כמה מקום הקצאת, אלא אם אתה שומר את זה במבנה נתונים מסויים(כפי שמתקיים בשפות "גבוהות" יותר).

קישור לתוכן
שתף באתרים אחרים

אז מה קורה אם אני עושה דבר כזה:

	char c[2];
int x=2;
scanf("%s", &c);
prinf("%s", c);

הערך של X נדרס?

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

קישור לתוכן
שתף באתרים אחרים

ההנחה שלי באה מתוך אסמבלר שכל המשתנים מוגדרים בזכרון אחד אחרי השני..

1) אתה יכול לשמור במחרוזת שלך רק תו אחד, מכיוון שתמיד התו האחרון הוא \0.

2) כשאתה רושם char c[2], התאים של המערך מקבלים ערך זבל שלא ניתן לדעת מה הוא.

3) כפי שכבר נרשם, SCANF לא יודע מה גודל המחרוזת, ולכן הוא ישים בכתובת שיש בC את התוים שהכנסת + '\0' בסוף.

4) אם תכניס ABC, יתכן ותדרוס בטעות מקום אחר בזיכרון.

5) CHAR*, או מערך של CHAR(כפי שרשמת בקוד שלך) הוא מצביע למקום בזיכרון. הקומפיילר של C לא מזהה את זה בתור מחרוזת, ולא יודע כמה מקום הקצאת, אלא אם אתה שומר את זה במבנה נתונים מסויים(כפי שמתקיים בשפות "גבוהות" יותר).

3-4 - אז אם אני יכניס ABC בSCANF אז בעצם גודל המערך של C גדל? או שבעצם מה שעומד מאחורי כל הקטע של STRING הוא שהתוכנית מדפיס את כל הערכים מהכתובת של C עד ל NULL (רק אל תענו כן בלי לפרט לאיזה חלק כן D= ).

5 - אז מה אתה אומר C לא שפת "עילית"?

אתה מתכוון לC++, כי שמה זה כמו בC.. אני מניח שבJAVA זה כמו שאמרת..

עריכה:

יש לי עוד שאלה שלא קשורה לנושא.

מצביע כל שהוא, לא משנה איזה סוג - איך הולך הקטע עם מערכים?

הוא מצביע על כל המערך או על ההתחלה שלו?

קישור לתוכן
שתף באתרים אחרים

אוקי אני לא כל כך בטוח אבל אני אנסה להסביר:

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

תאר את הזיכרון כרצף ארוף מאוד של תאים.

עכשיו למה מערכת ההפעלה תחפש דווקא 5 תאים ברצף ולא סתם תאים רנדומליים? כי ככה יותר נוח לגשת למערך.

עכשיו למחרוזות:

נניח אתה מגדיר מערך בגודל של 5 תאים מערך של תווים. מערכת ההפעלה מקצה לך 5 תאים לא תפוסים נניח שכתובות שלהם הן: מ 0x0001 עד 0x0005 (סתם נתתי כתובות לצורך דוגמא - יש לציין את לי מושג איך כתובות נשמרות אף פעם לא התעניינתי). כתובת 0x0006 תפוסה על ידי תוכנה אחרת או לא תפוסה בכלל ומכילה ערך זבל.

כאשר אתה משתמש ב scanf("%s"); הוא לא יודע כמה תוים הולכים להיקלט הוא פשוט ממשיך לכתוב את המידע למערך שלך עד שלא ילחץ enter. כאשר תנסה לקלוט את המחרוזת Hello שמכילה 6 תווים התו השישי יקלט לתוך תא עם כתובת 0x0006 ויבצר מצב שנקרא Buffer overflow. במקרה הזה התוכנית שלך תקרוס (טוב לפחות היא אמורה לקרוס לא בטוח אם היא באמת תקרוס).

עכשיו עוד דבר כאשר אתה כותב scanf("%s", &c); כשאר c מוגדר כ char c[5]; אתה בעצם אומר לתוכנה שלך: כל תו שיקלט ירשם בכתובת של c[0] (כאשר c[0] זה תחילת המערך בדומגא שלי 0x0001). עכשיו כשאר נקלט התו ראשון הוא נכתב לתוך כתובת 0x0001 השני יכתב לתוך 0x0002 שזה c[1] וכו'.

לא יודע יש מצב שכל מה שכתבתי פה זה שטויות (אני מקווה שלא :x ) כי בכל זאת השעה דיי מאוחרת ואני קצת שתוי. לפחות ככה אני מבין מרוזות יש מצב אני טועה. תחכה למומחים שיאשרו (או לא יאשרו :P) את מה שכתבתי.

עד אז בהצלחה ולילה טוב.

קישור לתוכן
שתף באתרים אחרים

(א)

ההנחה שלי באה מתוך אסמבלר שכל המשתנים מוגדרים בזכרון אחד אחרי השני..

(ב)

3-4 - אז אם אני יכניס ABC בSCANF אז בעצם גודל המערך של C גדל? או שבעצם מה שעומד מאחורי כל הקטע של STRING הוא שהתוכנית מדפיס את כל הערכים מהכתובת של C עד ל NULL (רק אל תענו כן בלי לפרט לאיזה חלק כן D= ).

(ג)

5 - אז מה אתה אומר C לא שפת "עילית"?

אתה מתכוון לC++, כי שמה זה כמו בC.. אני מניח שבJAVA זה כמו שאמרת..

(ד)

עריכה:

יש לי עוד שאלה שלא קשורה לנושא.

מצביע כל שהוא, לא משנה איזה סוג - איך הולך הקטע עם מערכים?

הוא מצביע על כל המערך או על ההתחלה שלו?

א) לא ניסיתי אף פעם(ואתה מוזמן לנסות ולרשום מה שיבלתי), אך אני מנחש שזה באמת מה שיקרה במקרים מסויימים, מאחר שהמשתנים בSTACK נשמרים אחד אחרי השני. זה גם תלוי בתוכן שהכנסת, מכיוון שבארכיטקטורות מסויימות כתובת של מצביע היא בכפולות של 4, ולכן יתכן שזה ידרוס שטח שלא בשימוש. במקרה שלנו זה כנראה לא ידרוס את הערך של X(מכיוון שמכניסים ל4 תאים), אך אם היית מכניס abcd, יתכן שזה היה דורס.

ב) המערך נשאר באותו גודל. פשוט יש חריגה בזיכרון, ואתה כותב למקום שאסור לך. בC, בשונה משפות גבוהות יותר, אין מבנה מוגדר שמציין מה זו מחרוזת, ולכן לא נשמר האורך שהקצאת לה. בשפות גבוהות יותר(C# וJAVA לדוגמא), המבנים של המחרוזת כבר מכנים לך בתוך השפה, ואתה לא צריך לדאוג לזה. בC++ יש אותו מצב כמו שיש בC. ההבדל בין C ל C++ הוא שיש ספריות שאנשים נחמדים אחרים כתבו שמכילות מבנים כאלה ואחרים(כולם נמצאים תחת הSTD).

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

ד) מצביע, כשמו הוא, מצביע למקום בזיכרון. המקום הזה יכול להתצביע על מספר נתונים ברצף אחד אחרי השני או על נתון אחד. התוכן של מצביע, הוא למעשה כתובת בזיכרון. כשאתה שולח לדוגמא לSCANF את &a, אתה שולח בעצם את הכתובת של a. כשאתה שולח את a אתה שולח את הכתובת שa מצביע עליה.

כשאתה מגדיר מערך, אתה מורה לקומפיילר שאתה מעוניין להשתמש ביותר מתא אחד בזיכרון. הוא עושה מה שהוא עושה(תלוי אם זה על הSTACK או על הHEAP), ומחזיר לך כתובת כלשהי. הכתובת הזו מציינת את הכתובת בזיכרון של התא הראשון בלבד. לפי המבנה שביקשת לשמור בכל תא, הקומפיילר יודע את הגודל של כל תא, ולכן הוא יודע לקפוץ לתא ספציפי(מה שנקרא RANDOM ACCESS, כמו בRAM). אם למשל הגדרת את c כמחרוזת(רצף של תווים/בתים בזיכרון), c יהייה בעצם מצביע לאות הראשונה במחרוזת. בית גודלו 1, ולכן האות השניה תהייה בc+1.

כדי לגשת לתוכן שמצביע מצביע עליו, ניתן לרשום גם *c(כמו c[0]), ותקבל את האות הראשונה. כדי לקבל את האות השניה ניתן לרשום *(c+1) או c[1]. בכתיב הוא שקול.

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

מערכת ההפעלה מחפשת מקום רק אם מקצים דינאמית על הHEAP.

והתאים לא יכולים להיות תפוסים ע"י תוכנות אחרות, מכיוון שהכתובות הן כתובות לוגיות(כלומר, יתכן שבתוכנית א משתנה שמור בכתובת X, וכך גם בתוכנית ב). הם יכולים להיות תפוסים ע"י התוכנית שלך, או ע"י קטע קוד קטן שמערכת ההפעלה מצרפת לכל תוכנית.

קישור לתוכן
שתף באתרים אחרים

הקומפיילר יודע מה הOFFSET של המערך בזיכרון(מבלוק הקוד האחרון שנפתח), וכל מקום שהוא רואה שרשום בו C הוא רושם את הOFFSET(באותו בלוק קוד). בפעם הבאה שירצו להגדיר משתנה הוא ישים אותו אחריו(בכתובת בכפולה של 4 בתים). כשאתה רוצה לגשת לתא במערך, המעבד יחשב את הOFFSET ועוד המקום שהבלוק התחיל ממנו, וכך הוא יגיע לכתובת הלוגית של המערך.

קישור לתוכן
שתף באתרים אחרים

עוד הערה קטנה שלא ציינו כאן. הסינטקס הזה אינו נכון:

char c[10];
scanf("%s", &c);

במקום זה צריך להשתמש ב:

char c[10];
scanf("%s", c);

הסיבה היא ש-scanf צריך לקבל מצביע. כיוון ש-c הוא מערך, הוא בעצם כבר מצביע (מערכים הם מצביעים ב-C).

קישור לתוכן
שתף באתרים אחרים

אם כבר אז נוח ועדיף יותר להשתמש בפונקציות קלט/פלט של מחרוזות puts gets - תחפש את התחביר בגוגל. gets קולטת עד האנטר ו scanf קולטת עד רווח. כלומר אם אתה רוצה לקלוט מחרוזת שמכילה 2 מילים, זה יקלוט רק את המילה הראשונה.

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

וכמו שנאמר אינדקסים, ה סוגרים האלה [] בעצם לוקחים את כתובת התחלה של המערך ומגיע לאיבר שצויין על ידי החישוב:

add adress + sizeof(סןג המערך)*index

כלומר אם הגדרת

int arr[10];
int x;
x = arr[6];
x= arr[15];

ההשמה הראשונה תקח את כתובת ההתחלה של המערך ותוסיף לכתובת 6*גודל int בבתים - ששם נשמר הערך.

ההשמה השנייה תקח את כתובת התחלת המערך ותוסיף לכתובת 15*גודל int בבתים - ששם אמור להישמר הערך. אבל פה אתה כבר גולש מהזכרון שהוקצע לך ע"י מערכת ההפעלה אבל אין פה שגיעת קומפלציה - ככה פשוט עובדים האינדקסים. (שניהם לא משנים את הכתובת ששמורה ב arr אלה מחשבים את הכתובת של התא שאליו אתה רוצה לגשת).

אם אתה קולט מחרוזת, ולא משאיר מקום ל NULL TERMINATOR (בהנחה שאתה לא מקבל שגיעת זמן ריצה עקב גלישה ופנייה לזיכרון לא חוקי) - מה שאתה מקבל בפועל זה סתם מערך של תווים. רוב פונקציות הסיפריה של string.h רצות על "המחרוזת" עד שהם לא מגיעות לסוף - שמצויין ע"י ה NULL TERMINATOR , במידה והוא לא קיים... הם יגלשו מהמחרוזת, מה שסביר להניח יתן טעות במקרה הטוב, שגיעת זמן ריצה במקרה הפחות טוב.

קישור לתוכן
שתף באתרים אחרים

לפני כמה זמן קיבלתי את אחת השגיעות המוזרות ביותר שיצא לי לקבל ב VS עקב פנייה לזכרון לא חוקי. לא יודע בדיוק מה קרה שם אבל כתיבה לכובת מסויימת דרסה כנראה מידע של ה VS או של מערכת ההפעלה וכתוצא בכך ה VS לא עבד. .. יותר נכון הוא עבד אבל לא בדיוק. כל ההקצעות ( אפילו הסטטיות לא עבדו - זרקו כתובות לא חוקיות וגם באיתחול סטטי ידני זרקו ערכים "זבליים") התעלם ממשתים, חלק לא הגדיר אפילו. משהו ממ שהזוי... יש תמנות מהדיבאגר... ממש לא מובן מה הלך שם. לא היה ניתן להריץ שום דבר ב VS.

קישור לתוכן
שתף באתרים אחרים

לא נראה לי הכי הבנתי תהסבר שלכם על מצביעים.

אמרתם שמערך הוא בעצם פוינטר שכיוויכול יש לו הגבלות...

כשאני מצביע על מערך אני יכול לעשות לו ככה *C++ ? זה אומר שהוא מצביע לתא הבא במערך? (ראיתי את הדרך שרשמת אבל אולי זוהי עוד דרך..)

כאשר אני משווה פוינטר למערך גם הפוינטר מוגבל?

קישור לתוכן
שתף באתרים אחרים

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.


×
  • צור חדש...