Access Violation ב-C...

p i n g o

New member
Access Violation ב-C...

כתבתי את התוכנית הבאה, שאמורה לקלוט רשימה של שמות ולשרשר את כולם באותו מערך, בצורה דינאמית (המערך יגדל עם כל שם שיוכנס), כשהשמות יופרדו ברווח. משום מה, למרות שהפונקציה עצמה עובדת כמו שצריך, עם היציאה מהפונקציה (או עם הפעלת הפונקציה הבאה - free() - התוכנית קורסת בגלל Access Violation. אם מישהו יוכל לשים את האצבע על מה לא בסדר בתוכנית, אני מאוד אשמח
. והנה קטע הקוד:
void input (char* ptr) { char name[]={0}; char* pointer=ptr; unsigned int size=0; int i; printf ("Please enter the class students' names:\n"); scanf ("%s", name); ptr = (char*)realloc(ptr,0); while (strcmp(name, "END")) { if (ptr==NULL) //allocation failed { printf ("not available"); exit (1); } else // if allocation succeeded, the program would continue { size = _msize (ptr); // ptr's size prior to the reallocation ptr = (char*)realloc(ptr, size+(strlen(name)+1)*sizeof(char)); if (ptr==NULL) //allocation failed { printf ("not available"); exit (1); } else for (i=0 ; size+i < size+strlen (name) ; ++i) { *(ptr+size+i)=name; } *(ptr+size+strlen(name))=' '; } scanf ("%s", name); } } int main (void) { char* ptr=0; input (ptr); free (ptr); }
 
לא בדקתי הכל,

אבל ברור רק מההתחלה שיש בעיה של זיכרון. אי אפשר לעשות SCANF לתוך BUFFER שאינו מספיק גדול בשביל להכיל את המידע. בתוכנית, המידע נקרא לתוך משתנה NAME, שגודלו הוא בדיוק בית אחד. איך הוא אמור להכיל שמות שהמשתמש מקיש? הגדר את NAME להיות מערך תוים בגודל מקסימלי כלשהו (נניח 256, או כתוב אלגוריתם שיגדיל אותו כשהקלט גדל).
 

p i n g o

New member
name הוא פוינטר קבוע מסוג char,

שאם קולטים לתוכו באמצעות %s ב-scanf, תיקלט מחרוזת ש-char יצביע לתו הראשון בה. בכל מקרה, הקליטה ל-name עובדת, וגם סידור השמות במערך החדש עובד (עקבתי אחרי המשתנים עם ה-Debugger ב-Visual C). הבעיה מתעוררת כשיוצאים מהפונקציה, יכול להיות שהבעיה היא סביב ה-free()...
 

p i n g o

New member
אוקיי, עכשיו זה באמת מוזר...

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

עכשיו הסתכלתי קצת יותר לעומק, ויש עוד בעיות: 1. המצביע PTR לא חוזר עם ערך חדש לתוכנית הראשית. מה שבעצם קורא, זה ש*עותק* של המצביע PTR נכנס לפונקציה. היא משנה אותו, מתעסקת איתו וכו'. כשהפונקציה מסתיימת, *העותק* של PTR נעלם. לתוכנית הראשית לא ממש איכפת מה קרה לעותק של PTR, כי היא עדיין מחזיקה ב-PTR המקורי, שערכו (לאחר הפעלת INPUT) הוא עדיין 0. שים לב לקוד הבא:
void input(char** ptr) { *ptr = (char*)malloc(100); strcpy(*ptr, "hello world"); } int main() { char* ptr = 0; input(&ptr); printf("%s", ptr); free(ptr); }​
הפעם, מכיוון שאני מעוניין שהפונקציה INPUT תעדכן את PTR *המקורי* (לא עותק לו), אני שולח לה את *הכתובת* של PTR (ראה את הקריאה ב-MAIN). הפונקציה INPUT כל הזמן משתנה את המשתנה PTR המקורי במקום את העותק שלו. לכן השינויים "יורגשו" גם אחרי INPUT.
 

p i n g o

New member
תודה על ההערות

באמת שמתי לב שהשינויים שעשיתי ב-PTR "הולכים לאיבוד" אחרי היציאה מהפונקציה ולא הבנתי מה לא עשיתי בסדר... בכל מקרה, שיניתי את הפונקציה בהתאם למה שהצעת לי, ועכשיו התוכנית קורסת ב-scanf (ניסיתי עם כוכבית, בלי כוכבית, אפילו אמפרסנט שמתי ועדיין קורס או לא מתקמפל בכלל)... אני בטוח ששכחתי כוכבית או אמפרסנט איפה שהוא... הנה הקוד:
void input (char** ptr) { char name[30]={0}; unsigned int size=0; int i; printf ("Please enter the class students' names:\n"); scanf ("%s", name); *ptr = (char*)realloc(*ptr,0); while (strcmp(name, "END")) { if (ptr==NULL) //allocation failed { printf ("not available"); exit (1); } else // if allocation succeeded, the program would continue { size = _msize (*ptr); // ptr's size prior to the reallocation *ptr = (char*)realloc(*ptr, size+(strlen(name)+1)*sizeof(char)); if (*ptr==NULL) //allocation failed { printf ("not available"); exit (1); } else for (i=0 ; size+i < size+strlen (name) ; ++i) { **(ptr+size+i)=name; } **(ptr+size+strlen(name))=' '; } scanf ("%s", *name); } } int main (void) { char* ptr=0; input (&ptr); free (ptr); }
 
ניחושים...

1. אתה יכול לתת דוגמה לשם שאתה מנסה לקלוט? האם הוא מכיל רווחים? אם אתה אומר שהקוד שלך נופל ב-SCANF. האם מדובר על הפעם הראשונה ש-SCANF מופעל? אני מנחש שלא, אבל חשובה לי תשובתך... 2. למה אתה עושה REALLOC ל-PTR ישר בהתחלה? יותר פשוט שהפונקציה INPUT תאפס אותו בהתחלה, ואח"כ, כל פעם שהוא צריך לגדול, היא תבצע REALLOC. לא המשכתי לקרוא בינתיים, כי אני קצת קצר בזמן... תבדוק את הנ"ל.
 

p i n g o

New member
נמאס לי מה-scanf

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

p i n g o

New member
נמאס לי מה-scanf

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

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

p i n g o

New member
עוד שאלה...

נתון מערך של מילים שמופרדות ברווחים. (מספר המילים לא קבוע) אני אמור להפריד את המילים ולשים אותן במערך דו-מימדי (מערך של מצביעים למחרוזות...) ככה שכל שורה במערך תהיה מילה מהמערך המקורי, ויהיו מספר שורות כמספר המילים, שהוא כאמור לא קבוע. ניסיתי לכתוב משהו, וזה לא עובד לי, הנה הקוד:
void arrange (char*** matrix, char** ptr) { int i,j=0; *matrix = ptr; for (i=0 ; i < (int)(_msize (*ptr)/sizeof(char)) ; ++i) { if (*(*matrix+i)==' ') { matrix[j]=*(matrix+i+1); ++j; } } }​
מה פה לא בסדר?
 
לצערי הכל... ../images/Emo13.gif

קודם כל, אם אתה טוען שמשהו לא עובד לך, תן דוגמה לקלט ולפלט המתקבל. אל תפרסם רק את התוכנית בהודעה. זה לא מספיק. אם היא עפה, תסביר באיזו שורה. נסה לעבור עם DEBUGGER ולראות אם ערכי המשתנים הם מה שאתה מצפה שיהיו. כמה אבחנות שאני מסוגל לעשות לפי התוכנית: 1. אל תשתמש ב-_MSIZE. השיטה שלך לחישוב מספר איברי המערך היא שגויה. אתה צריך להעביר את גודל המערך כארגומנט לפונקציה. 2. כל פעם שאתה מוצא סוף מחרוזת, לא מספיק לשמור את תחילתה באיבר במערך, אלא עליך גם לשים 0 בסופה. אחרת, תחשוב מה יקרה כשתדפיס את המחרוזת שנמצאת באינדקס 0 במערך המחרוזות שלך: תודפס כל המחרוזת המקורית. זה כמובן אומר שעליך לשנות את המחרוזת המקורית. אם זה מקובל עליך, סבבה. אם לא, תיאלץ להקצות עותק של המחרוזת המקורית ולקשר אותו איכשהו למערך המחרוזות (לדוגמה ע"י הקצאת אינדקס 0 במערך, ולדאוג ששאר האינדקסים פשוט יצביעו לכתובות ביחס אליו). 3. אין צורך להעביר PTR מסוג CHAR**. מספיק להעביר CHAR*, וזאת כי אתה לא מעוניין לשנות את המצביע PTR בתוך הפונקציה. אתה רק רוצה להתייחס לתוכנו. זה גם יפשט את כל ההתעסקות במצביעים בתוך הפונקציה ויעזור לי להבין מה עוד קורה שם. 4. איפה בדיוק אתה מקצה את איברי MATRIX? אם MATRIX הוא מערך של מחרוזות, אז איפה שהוא אמורה להיות הקצאה של מערך בגודל X, כאשר כל איבר הוא מחרוזת (CHAR*). אין את זה פה. 5. אתה לא יכול להתייחס ל-MATRIX גם כמקור וגם כיעד. מצד אחד, אתה ניגש אליו כדי להגיע לאיברי המחרוזת המקורית. מצד שני, אתה מעדכן אותו כאילו הוא היה מערך. תפצל את השימושים: סרוק את PTR בעזרת משתנה עזר (לא MATRIX), והצב ב-MATRIX את ערכי משתנה העזר שמצאת בכל איטרציה. תראה, ניסיתי לפרט כמה מוקדים עיקריים, שמהווים בעיה בקוד הזה. אני לא יודע אם הצלתי להעביר את המסר או רק לבלבל אותך. לדעתי כדאי לך לחזור על חומר בסיסי במצביעים לפני שתמשיך, כי די ברור שדרוש לך תרגול בסיסי יותר לפני שאתה ניגש לבעיות מהסוג הזה. נסה לפרק את הבעיה שלך לכמה חלקים: 1. איך מחזירים מערך דינאמי מפונקציה 2. איך דואגים להגדיל "עם הזמן" מערך דינאמי 3. איך מפרקים מחרוזת לחלקים ומחזירים מצביע לכל חלק וכו'. בהצלחה.
 

p i n g o

New member
../images/Emo122.gif

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

1. אמרת שגודל המערך תלוי בגודל הקלט של המשתמש. אתה לא יכול לברר את גודל הקלט מהמשתמש לפני הקריאה לפונקציה? אחרי שהמשתמש סיים להזין את הקלט, אתה בוודאי יכול לברר כמה נקלט. 2. נניח ומצאת את התו " " (רווח) אינדקס 5 במחרוזת המקורית... כלומר 5 התווים הראשונים (0 עד 4) אמורים להיות מחרוזת אחת. מצביע אליהם אמור להיכתב באינדקס 0 במטריצה. נכון? נכון. עכשיו תחשוב מה יקרה אם תבקש להדפיס את המחרוזת באינדקס 0 במטריצה. הוא ידפיס לך את כל המחרוזת המקורית! למה? למה לא? הרי אין שום סימון מיוחד שיאמר למחשב שבתו 5 מסתיימת המחרוזת "הראשונה". *אתה* צריך לסמן את זה ע"י החלפת ה" " בתו 0. למעשה, ישנה פונקציה ב-C שעושה את כל בשבילך: STRTOK. אבל אני מניח שלא רוצים שתשתמש בה, אחרת לא היו נותנים לך ליישם את הכל. 4. כמו שאמרתי, הבעיה שאתה צריך לדעת לפתור היא לא פשוטה במקרה הזה. אתה צריך לדעת איך להגדיל מערך "עם הזמן", בהתאם למקום הדרוש עבורו... בשביל זה יש פונקציה כמו REALLOC. זה דומה למה שעשית בשאלה הקודמת שלך. 5. זה עניין קריטי. אתה חייב למצוא דרך אחרת לעשות את זה. אל תערב INPUT ב-OUTPUT - לא במקרה הזה בכל אופן. הכי פשוט: קח 2 מצביעי CHAR* זמניים, אתחל אותם ל-PTR ו"קדם" אחד מהם ב-1 כל פעם, עד שהוא יצביע על התו " ". מה גילית? המצביע ש*לא* הזזת מצביע על תחילתה מחרוזת אחת. עכשיו, אתה מאתחל שוב את שני המצביעים לתו שאחרי ה" ", וממשיך משם את אותו התהליך. בכל פעם שאתה מוצא " ", אתה שומר את תחילת המחרוזת הנוכחית ב-MATRIX.
 
למעלה