由于发布问题,我的上一个问题(此重复)已关闭。
最后提供了背景信息,以便您直接解决问题。
我有一个基于文本的程序来帮助学习词汇或其他任何东西(基本上模拟闪存卡,但闪现你不常了解的那些)。在我测试时它看起来工作正常,直到我厌倦了屏幕上不断积压的文本,所以我实现了一个(有点不可移植)清晰的屏幕例程。 然后它开始抛出异常,我输入了各种调试代码来尝试跟踪它。
嗯......我设法将它缩小到第445行的以下命令:
system("cls");
此命令如何导致异常?有没有人知道解决方法? 我在Windows Vista和Windows 7上的命令提示中都运行了相同的结果。
完整的来源以防有人想自己编译或浏览一下:
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <process.h>
#include <ctype.h>
#include <string.h>
#define DINPUTFILENAME "vtdb.~sv"
#define DOUTPUTFILENAME "vtdb.~sv"
#define MAXINTVALUE 2147483647
#define MAXTEXTLENGTH 256
#define N2LTONORM 5
#define NORMTON2L 3
#define NORMTOKNOWN 5
#define KNOWNTONORM 2
#define KNOWNTOOLD 2
#define OLDTONORM 1
struct vocab
{
int index; //identifies the entry in the list, allowing it to be selected by use of a random number
char * question;//pointer to question text
char * answer;//pointer to the answer text, which is required for the response to be considered correct
char * info;//pointer to optional extra text giving advice such as to how to format the response
char * hint;//pointer to optional text giving a clue to the answer
int right;//indicates whether counter is counting correct or incorrect responses
int counter;//counts how many times in a row the answer has been correct/incorrect
int known;//indicates to what level the vocab is known, and thus to which list it belongs (when loading/saving)
struct vocab * next;//pointer to next in list
};
struct listinfo//struct holds head, tail and the number of entries for the n2l, norm, known and old lists
{
struct vocab * head;
int entries;
struct vocab * tail;
};
struct listinfo n2l, norm, known, old;
int n2l_flag; //Prevents 'need to learn's coming up twice in a row
int maxtextlength = MAXTEXTLENGTH; //allows use of this #define within text strings
FILE * inputfile;
FILE * outputfile;
void getrecordsfromfile(char * inputfilename,char separator);//load
char * readtextfromfile(int maxchars,char separator);//get text field from file
int readnumberfromfile(int maxvalue,char separator);//get integer field from file
struct vocab * addtolist(struct vocab * newentry, struct listinfo * list);//add given (already filled in) vocab record to given list
int removefromlist(struct vocab * entry, struct listinfo * list,int freeup);//remove given entry from given list. Also destroy record if freeup is true
void reindex (struct listinfo * list);//necessary to stop gaps in the numbering system, which could cause random vocab selection to fail
int writeliststofile();//save
void testme();//main code for learning vocab, including options menu
char * gettextfromkeyboard(char * target,int maxchars);//set given string (char pointer) from keyboard, allocating memory if necessary
int getyesorno();//asks for yes or no, returns true (1) if yes
void testrandom();//code keeps causing exceptions, and as it's so random, I'm guessing it's to do with the random numbers
void getrecordsfromfile(char * inputfilename,char separator)
{
int counter = 0;
struct vocab * newvocab;
struct listinfo * newvocablist;
if (!(inputfile = fopen(inputfilename, "r")))
{
printf("Unable to read input file. File does not exist or is in use.\n");
}
else
{
printf("Opened input file %s, reading contents...\n",inputfilename);
while (!feof(inputfile))
{
newvocab = (struct vocab *)malloc(sizeof(struct vocab));
if (!newvocab)
{
printf("Memory allocation failed!\n");
return;
}
else
{
newvocab->question=readtextfromfile(MAXTEXTLENGTH,separator);
newvocab->answer=readtextfromfile(MAXTEXTLENGTH,separator);
newvocab->info=readtextfromfile(MAXTEXTLENGTH,separator);
newvocab->hint=readtextfromfile(MAXTEXTLENGTH,separator);
newvocab->right=readnumberfromfile(1,separator);
newvocab->counter=readnumberfromfile(0,separator);
newvocab->known=readnumberfromfile(3,separator);
switch (newvocab->known)
{
case 0: newvocablist = &n2l;break;
case 1: newvocablist = &norm;break;
case 2: newvocablist = &known;break;
case 3: newvocablist = &old;break;
}
addtolist(newvocab,newvocablist);
if (newvocab->question==NULL||newvocab->answer==NULL)
{
printf("Removing empty vocab record created from faulty input file...\n");
removefromlist(newvocab,newvocablist,1);
}
else counter++;
}
}
fclose(inputfile);
printf("...finished.\n%i entries read from %s.\n\n",counter,inputfilename);
}
return;
}
char * readtextfromfile(int maxchars,char separator)
{
int i=0;
char ch;
char * target = (char *)malloc(maxchars+1); //allocate memory for new string
if (!target) {printf("Memory allocation failed!\n");return 0;}//return 0 and print error if alloc failed
ch=getc(inputfile);
if (ch==separator){free(target);return NULL;}//if field is blank (zero-length), return null pointer
while (isspace(ch))
{
ch = getc(inputfile);//cycle forward until you reach text
if (ch == separator||ch=='\n'||ch==EOF) {free(target);return NULL;}//if no text found(reached ~ before anything else), return null pointer
}
if (ch=='"') //Entry is in quotes (generated by excel when exporting to .csv and field contains a comma)
{
ch=getc(inputfile);//move to next character after the quotes
while (i<maxchars && ch!='"' && ch!='\n')//stop when you reach the end quotes, end of line, or when text too long
{
target[i++]=ch;
ch = getc(inputfile); //copy name from file to target, one char at a time
}
}
else //entry is not in quotes, so char is currently first letter of string
{
while (i<maxchars && ch!=separator && ch!='\n')//stop when you reach separator, end of line, or when text too long
{
target[i++]=ch;
ch = getc(inputfile); //copy name from file to target, one char at a time
}
}
target[i] = '\0';//terminate string
return target;
}
int readnumberfromfile (int maxvalue,char separator)
{
int number, i=0;
char ch;
char * buff = (char *)malloc(11);
if (!buff) {printf("Memory allocation failed!\n");return 0;}//return 0 and print error if alloc failed
if (!maxvalue) maxvalue=MAXINTVALUE;
ch=getc(inputfile);
while (!isdigit(ch))
{
ch = getc(inputfile);//cycle forward until you reach a digit
if (ch == separator||ch=='\n'||ch==EOF) {printf("Format error in file\n");return 0;}//if no number found(reached ~ before digit), print error and return 0
}
while (i<11 && ch!=separator && ch!='\n')//stop when you reach '~', end of line, or when number too long
{
buff[i++]=ch;
ch = getc(inputfile); //copy number from file to buff, one char at a time
}
buff[i] = '\0';//terminate string
number = atoi(buff)<=maxvalue ? atoi(buff) : maxvalue;//convert string to number and make sure it's in range
free(buff);
return number;
}
struct vocab * addtolist(struct vocab * newentry, struct listinfo * list)
{
if (!list->head)//if head is null, there is no list, so create one
{
list->head = list->tail = newentry;//this is the new head and tail
list->entries = newentry->index = 1;
newentry->next = NULL;// FISH! not sure if this is necessary, but just be sure...
}
else//just appending to the list
{
list->tail->next = newentry;//adjust current tail to point to new entry
list->tail = newentry;//make the new entry the new tail
newentry->index=++list->entries;
newentry->next = NULL;
}
return newentry;
}
int removefromlist(struct vocab * entry, struct listinfo * list,int freeup)
{
struct vocab * prev;
if (list->head == entry) //if entry being deleted is first in the list
{
if (list->tail == entry) //if entry is only item in the list
{
list->head = list->tail = NULL;
}
else //if first in list, but not last
{
list->head = entry->next;
}
}
else //entry is not first in list, so set prev to point to previous entry
{
prev = list->head;
while (prev->next!=entry)
{
prev=prev->next;
if (!prev)
{
printf("Trying to delete an entry from a list it's not in!!\n");
return 0;
}
}
if (list->tail == entry)//if entry is at the end of the list
{
list->tail = prev;
list->tail->next = NULL;
}
else //if entry is somewhere in middle of list
{
prev->next=entry->next;
}
}//this entry is now not pointed to in any list
list->entries--;
/*following line removed because it could theoretically break a list if the entry was removed from a list after it had been added to another
entry->next = NULL;//and doesn't point to anything either*/
reindex(list);
if (freeup) //if freeup is set, this also wipes the record and frees up the memory associated with it
{
free(entry->question);
free(entry->answer);
free(entry->info);
free(entry->hint);
free(entry);
}
return 1;
}
void reindex (struct listinfo * list)
{
int counter = 1;
struct vocab * workingentry = list->head;
while (workingentry)
{
workingentry->index = counter++;
workingentry=workingentry->next;
}
if (list->entries!=counter-1) printf("Reindexing Error!\n");
}
int writeliststofile()
{
int i,counter=0;
struct listinfo * list;
struct vocab * entry;
if (!(outputfile = fopen(DOUTPUTFILENAME, "w")))
{
printf ("Error accessing output file!\n");
return 0;
}
else
{
printf("Saving...\n");
for (i=0;i<=3;i++)
{
switch (i)
{
case 0: list = &n2l;break;
case 1: list = &norm;break;
case 2: list = &known;break;
case 3: list = &old;break;
default: printf("Loop Error!\n");break;
}
entry=list->head;
while (entry!=NULL)
{
if (counter) fprintf(outputfile,"\n");
fprintf(outputfile,"%s~%s~%s~%s~%i~%i~%i",entry->question,entry->answer,entry->info,entry->hint,entry->right,entry->counter,i);
entry=entry->next;
counter++;
}
}
fclose(outputfile);
printf("...finished. %i entries saved.\n",counter);
return 1;
}
}
void testme()
{
int list_selector, entry_selector, bringupmenu = 0, testagain=1;
char testmenuchoice = '\n';
char * youranswer = (char *)malloc(MAXTEXTLENGTH+1);
struct listinfo * currentlist;
struct vocab * currententry;
if (!youranswer) {printf("Memory allocation error!\n");return;}
while (testagain)
{
fprintf(stderr,"Start of 'testagain' loop\nClearing screen...\n");
system("cls");
//select a list at random, using the percentage probabilities in the if statements. FISH! Can this be done with a switch and ranges?
fprintf(stderr,"Assigning list selector to random value...");
list_selector = (((float)rand() / 32768) * 100)+1;
fprintf(stderr,"assigned list selector value %i\nAssigning list pointer...",list_selector);
if (list_selector<33) currentlist = &n2l;
if (list_selector>32&&list_selector<95) {n2l_flag=0;currentlist=&norm;} //use norm list and cancel n2l flag (not cancelled with other lists)
if (list_selector>94&&list_selector<100) currentlist = &known;
if (list_selector==100) currentlist = &old;
fprintf(stderr,"assigned list pointer %x\nModifying pointer...",currentlist);
//do a little control over random selection
if (currentlist==&n2l && n2l_flag) {currentlist=&norm; n2l_flag=0;} //if n2l list was used last time as well (flag is set), use entry from the norm list instead
if (currentlist==&n2l) n2l_flag = 1; //is using n2l this time, set flag so it won't be used next time as well
if (currentlist->entries==0) currentlist = &norm;//if current list is empty, default to normal list
if (currentlist->entries==0 && !n2l_flag) currentlist = &n2l;//if normal list is empty, try n2l list if it wasn't used last time
if (currentlist->entries==0 && list_selector%10==5) currentlist = &old;//if list is still empty, in 10% of cases try old list
if (currentlist->entries==0) currentlist = &known;//in the other 90% of cases, or if old is empty, use the known list
if (currentlist->entries==0) currentlist = &old;//if known list is empty, try the old list
if (currentlist->entries==0) {currentlist = &n2l;n2l_flag=1;}//if old list is empty, use n2l list EVEN if it was used last time
if (currentlist->entries==0) {printf("No entries in list!");return;} //if list is STILL empty, abort
fprintf(stderr,"modified list pointer\nAssigning entry selector...");
//we now have the desired list of words with at least one entry, let's select an entry at random from this list
entry_selector = (((float)rand() / 32767) * currentlist->entries)+1;
fprintf(stderr,"assigned entry selector value %i\nAssignig pointer...",entry_selector);
currententry = currentlist->head;
fprintf(stderr,"set entry pointer to head, going to loop to it...\n");
while (currententry->index!=entry_selector)
{
currententry = currententry->next;//move through list until index matches the random number
if (currententry==NULL) {printf("Indexing error!\nCurrent list selector: %i, entries: %i, entry selector: %i\n",list_selector,currentlist->entries,entry_selector);return;}//in case not found in list
}
fprintf(stderr,"Looped, testing.\n");
printf("Translate the following:\n\n\t%s\n\n",currententry->question);
if (!currententry->info) printf("There is no additional information for this entry.\n");
else printf("Useful Info: %s\n\n",currententry->info);
printf("Your Translation:\n\n\t");
gettextfromkeyboard(youranswer,MAXTEXTLENGTH);
if (!strcmp(youranswer,currententry->answer))//if you're right
{
printf("Yay!\n");
if(currententry->right) currententry->counter++;
else currententry->right = currententry->counter = 1;
if (currententry->counter>2) printf("You answered correctly the last %i times in a row!\n",currententry->counter);
//make comments based on how well it's known, and move to a higher list if appropriate
if (currentlist==&n2l && currententry->counter>=N2LTONORM)
{
removefromlist(currententry,currentlist,0);
printf("Looks like you know this one a little better now!\nIt will be brought up less frequently.\n");
currententry->counter = 0;
addtolist(currententry,&norm);
}
if (currentlist==&norm && currententry->counter>=NORMTOKNOWN)
{
removefromlist(currententry,currentlist,0);
printf("Looks like you know this one now!\nIt will be brought up much less frequently.\n");
currententry->counter = 0;
addtolist(currententry,&known);
}
if (currentlist==&known && currententry->counter>=KNOWNTOOLD)
{
removefromlist(currententry,currentlist,0);
printf("OK! So this one's well-learnt.\nIt probably won't be brought up much any more.\n");
currententry->counter = 0;
addtolist(currententry,&old);
}
}
else //if you're wrong
{
printf("\nSorry, the correct answer is:\n\n\t%s\n\n",currententry->answer);
if(!currententry->right) currententry->counter++;
else {currententry->right = 0; currententry->counter = 1;}
if (currententry->counter>1) printf("You've got this one wrong the last %i times.\n",currententry->counter);
if (currentlist==&norm && currententry->counter>=NORMTON2L)
{
removefromlist(currententry,currentlist,0);
printf("This one could do with some learning...\n");
currententry->counter = 0;
addtolist(currententry,&n2l);
}
if (currentlist==&known && currententry->counter>=KNOWNTONORM)
{
removefromlist(currententry,currentlist,0);
printf("OK, perhaps you don't know this one as well as you once did...\n");
currententry->counter = 0;
addtolist(currententry,&norm);
}
if (currentlist==&old && currententry->counter>=OLDTONORM)
{
removefromlist(currententry,currentlist,0);
printf("This old one caught you out, huh? It will be brought up a few more times to help you remember it.\n");
currententry->counter = 0;
addtolist(currententry,&norm);
}
}
fprintf(stderr,"Tested, options menu?\n");
printf("Type 'o' for options or strike enter for another question\n");
testmenuchoice = getchar();
fprintf(stderr,"Got choice\n");
if (tolower(testmenuchoice)=='o') bringupmenu = 1;
fprintf(stderr,"set menuflag\n");
if (testmenuchoice!='\n') while (getchar()!='\n')getchar();
fprintf(stderr,"cleared getchar\n");
while (bringupmenu)
{
system("cls");
printf("Current Entry:\n\nQuestion: %s\nAnswer: '%s'\n",currententry->question,currententry->answer);
if (currententry->info) printf("Info: %s\n",currententry->info); else printf("No info.\n");
if (currententry->hint) printf("Hint: %s\n\n",currententry->hint); else printf("No hint.\n\n");
printf("Options Menu:\n\nType q to modify the question phrase displayed for translation.\nType a to change the answer phrase you must provide.\nType i to add/modify additional info for this entry.\nType h to add/modify the hint for this entry.\nType p to mark this entry as high priority to learn.\nType d to delete this entry from the database.\nType x to end testing and return to the main menu.\n\n");
testmenuchoice=getchar();
while (getchar()!='\n') getchar();
switch (testmenuchoice)
{
case 'q': printf("Enter new question text for this entry (max %i chars):\n",maxtextlength);
currententry->question=gettextfromkeyboard(currententry->question,MAXTEXTLENGTH);
break;
case 'a': printf("Enter new answer text for this entry (max %i chars):\n",maxtextlength);
currententry->answer=gettextfromkeyboard(currententry->answer,MAXTEXTLENGTH);
break;
case 'i': printf("Enter new info for this entry (max %i chars):\n",maxtextlength);
currententry->info=gettextfromkeyboard(currententry->info,MAXTEXTLENGTH);
break;
case 'h': printf("Enter new hint for this entry (max %i chars):\n",maxtextlength);
currententry->hint=gettextfromkeyboard(currententry->hint,MAXTEXTLENGTH);
break;
case 'p': if(currentlist=&n2l)printf("Already marked as priority!\n");
else
{
removefromlist(currententry,currentlist,0);
currententry->counter = 0;
currentlist=&n2l;
addtolist(currententry,currentlist);
printf("Entry will be brought up more often\n");
}
break;
case 'd': printf("Are you sure you want to delete this entry?\nOnce you save, this will be permanent!(y/n)");
if (getyesorno()) {removefromlist(currententry,currentlist,1);printf("Entry deleted!\n");bringupmenu=0;}
else printf("Entry was NOT deleted.\n");
break;
case 'x': bringupmenu = testagain = 0;
break;
default: printf("Invalid choice.\n");
}
if (bringupmenu)
{
printf("Select again from the options menu? (y/n)");
bringupmenu = getyesorno();
}
if (!bringupmenu&&testagain)
{
printf("Continue testing? (y/n)");
testagain = getyesorno();
}
}
fprintf(stderr,"End of 'testagain' loop.\n Clearing Screen...");
system("cls");
}
free(youranswer);
// getchar();
return;
}
char * gettextfromkeyboard(char * target,int maxchars)
{
int i =0;
char ch;
if (!target)//if no memory already allocated (pointer is NULL), do it now
{
target=(char *)malloc(maxchars+1);
if (!target) {printf("Memory allocation failed!");return NULL;} //return null if failed
}
ch = getchar();
if (ch=='\n') {free(target);return NULL;}//if zero length, free mem and return null pointer
while (!isalnum(ch))//cycle forward past white space
{
ch=getchar();
if (ch=='\n') {free(target);return NULL;}//if all white space, free mem and return null pointer
}
while (ch!='\n' && i<maxchars)
{
target[i++]=ch;
ch=getchar();
}
target[i]='\0';
return target;
}
int getyesorno()
{
char yesorno = '\n';
while (toupper(yesorno)!='Y'&&toupper(yesorno)!='N')
{
yesorno=getchar();
if (toupper(yesorno)!='Y'&&toupper(yesorno)!='N') printf("Invalid choice. You must enter Y or N:\n");
}
while (getchar()!='\n') getchar();
if (toupper(yesorno)=='Y') return 1;
else return 0;
}
void testrandom()
{
return;
}
int main(int argc, char* argv[])
{
char * inputfilename = DINPUTFILENAME;
char * outputfilename = DOUTPUTFILENAME;
char separator = '~';
char menuchoice = '\0';
n2l.entries = norm.entries = known.entries = old.entries = 0;
srand((unsigned)time(NULL));
fprintf(stderr,"Start...\n");
printf("Loading...\nLoad default database? (y/n)");
if (!getyesorno())
{
printf("Default file type is .~sv. Import .csv file instead? (y/n)");
if (getyesorno())
{
separator = ',';
printf("Enter name of .csv file to import:\n");
}
else
{
printf("Enter name of .~sv file to load:\n");
}
inputfilename = gettextfromkeyboard(inputfilename,256);
}
getrecordsfromfile(inputfilename,separator);
while (menuchoice!='x')
{
printf("Welcome to the Vocab Test, version C!\n\nMain menu:\n\n\tt: Test Me!\n\ts: Save\n\tx: Exit\n\n");
menuchoice = getchar();
while (getchar()!='\n') getchar();
switch (tolower(menuchoice))
{
case 'x': break;
case 't': testme(); break;
case 's': writeliststofile();break;
case 'w': testrandom(); break;
default: printf("Invalid choice. Please try again.\n"); break;
}
system("cls");
}
system("cls");
printf("Bye for now!\n\nPress enter to exit.");
getchar();
fprintf(stderr,"Successfully closed\n");
return 0;
}
我尝试在典型的运行中添加stderr的输出,但它使得身体太长。还尝试将其添加为答案,但是:
声誉低于100的用户在询问后8小时内无法回答自己的问题。你可以在7个小时内自我回答。在此之前,请使用评论或编辑您的问题。
背景:今年早些时候我第一次尝试编程,并决定在开始使用C ++,Java,以及Python和C#之前,先从C开始。 为了让我从C开始,在强制性的hello世界之后,我写了一个小游戏(基于文本,也包括“cls”命令),然后转移到这个小词汇测试器,这是为了帮助我学习印尼语,而我是在奥地利说德语:-D。我最终在cls崩溃时感到恼怒,并且从那时起就没有编程。我真的想把它拿起来,所以我从这个问题开始。
答案 0 :(得分:1)
您是否尝试过打印'\f'
?那是“formfeed”角色。
例如,在gettextfromkeyboard
中,如果只输入空格,它将释放target
,即使在输入时为非NULL。
在这一行:
inputfilename = gettextfromkeyboard(inputfilename,256);
它将指向常量字符串的inputfilename
传递给gettextfromkeyboard
。试图释放这是一个坏主意。
我也怀疑while (getchar()!='\n') getchar();
。
假设输入为"ABC\n"
。
条件将消耗并返回'A'
,循环体将消耗'B'
,条件将消耗并返回'C'
,并且循环体将消耗{{ 1}}。
答案 1 :(得分:1)
尝试使用其中一个 curses , ncurses 等软件包。如果它不是太常见,那么网络上应该有一个适用于您的C版本的地方。它应该处理各种文本屏幕功能,包括清除屏幕,它非常便携。
答案 2 :(得分:0)
可能系统('cls')已被弃用? Here是另一种方式。
答案 3 :(得分:0)
代码运行正常,CLS命令有效。不可重现的崩溃通常表明内存损坏。我想说一个好看的地方是你的readtextfromfile函数,因为如果一个文件包含256个字符,它会覆盖输入缓冲区。