I'm trying to add a string to the next index of my array, however I'm having this odd issue where when Im adding to any index other than 1 (yes, I'm indexing my array from one, just to match the actual number of the item for simplicity, I'm sorry!). Essentially, if I ass to index 1, everything is fine, however when I add to index 2, the value of index 1 seems to change to the value I'm planning to put into index 2, even before the lines of code where I add the value to index 2. For example:
Say I try to add 'test1' to my array queuedHashed[100][101] at index 1, and I run through all printing all elements of the array, next to index number, I get:
1: test1
However when I go to add 'test2' at index 2 to the array, I would get:
1: test2
2: test2
I've been pulling my hair out for hours trying to fix this, and can't see where I'm going wrong. I'm currently passing my variables around using structs (due to GTK limitations), however this issue also persisted when the variables were global variables, before I changed them to local ones.
Here is my code:
queue_hash function:
static void queue_hash (GtkButton *button, gpointer user_data) {
struct data *dataStruct = user_data;
GtkWidget *hashWid = dataStruct->hash;
GtkWidget *hashTypeWid = dataStruct->hashType;
GtkWidget *hashEntryLabel = dataStruct->hashEntryLabel;
GtkListStore *store;
GtkTreeIter iter;
const char* hash = gtk_entry_get_text(GTK_ENTRY(hashWid));
int hashLen = gtk_entry_get_text_length(GTK_ENTRY(hashWid));
int hashTypeIndex = gtk_combo_box_get_active(GTK_COMBO_BOX(hashTypeWid));
store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
// TODO: Update max length to 128
if (hashLen > 100) {
gtk_widget_set_name(hashWid, "fieldsError");
gtk_label_set_text(GTK_LABEL(hashEntryLabel), "Hash exceeds max length of 100");
g_print("Hash length exceeds 100: Exiting.");
return;
}
gtk_widget_set_name(hashWid, "");
gtk_widget_set_name(hashTypeWid, "");
gtk_widget_set_name(hashEntryLabel, "");
gtk_label_set_text(GTK_LABEL(hashEntryLabel), "Hash to be cracked:");
if ((strcmp(hash, "") == 0) || (hashTypeIndex == -1)) {
if (strcmp(hash, "") == 0) {
gtk_widget_set_name(hashWid, "fieldsError");
}
if (hashTypeIndex == -1) {
gtk_widget_set_name(hashTypeWid, "fieldsError");
}
g_print("Invalid Entry \n");
} else {
// Check for spaces in hash - return if found
// TODO: Check for other non-alphabetical chars/symbols
for (int i = 0; i < hashLen; i++) {
if (hash[i] == ' ') {
gtk_widget_set_name(hashWid, "fieldsError");
gtk_widget_set_name(hashEntryLabel, "errorLabel");
gtk_label_set_text(GTK_LABEL(hashEntryLabel), "Please remove all spaces");
g_print("Space found in hash: Exiting\n");
return;
}
}
g_print("//////////////////////////////\n");
g_print("Before: (HashCount: %i)\n", dataStruct->hashCount);
//test_queue(dataStruct->queuedHashes, dataStruct->hashCount);
for (int i = 1; i <= dataStruct->hashCount; i++) {
g_print("%i: %s\n", i, dataStruct->queuedHashes[i][0]);
}
sleep(1);
// Save hash to array
++dataStruct->hashCount;
g_print("After Increment: %i\n", dataStruct->hashCount);
g_print("Hash: %s\n", hash);
dataStruct->queuedHashes[dataStruct->hashCount][0] = hash; // Line to actually add new string to array
dataStruct->queuedHashTypes[dataStruct->hashCount] = hashTypeIndex;
g_print ("Queue Hash: %s %i\n", dataStruct->queuedHashes[dataStruct->hashCount][0], dataStruct->queuedHashTypes[dataStruct->hashCount]);
sleep(1);
g_print("After: (HashCount: %i)\n", dataStruct->hashCount);
//test_queue(dataStruct->queuedHashes, dataStruct->hashCount);
g_print("Manual 1: %s, 2: %s\n", dataStruct->queuedHashes[1][0], dataStruct->queuedHashes[2][0]);
for (int i = 1; i <= dataStruct->hashCount; i++) {
g_print("%i: %s\n", i, dataStruct->queuedHashes[i][0]);
}
}
Part of calling function that calls the above function:
struct data *hash_data = g_new0(struct data, 1);
hash_data->hash = hashEntry;
hash_data->hashType = hashSelect;
hash_data->hashEntryLabel = hashEntryLabel;
g_signal_connect(queueButton, "clicked", G_CALLBACK (queue_hash), hash_data);
Global struct definition:
struct data {
char* queuedHashes[100][101];
int queuedHashTypes[100];
int hashCount;
GtkWidget *hash;
GtkWidget *hashType;
GtkWidget *hashEntryLabel;
GtkTreeSelection *selectedHash;
};
I have a lot of print statements in there to help illustrate where things seem to be changing unexpectedly, here is the output of the program when run, and two values are entered:
//////////////////////////////
Before: (HashCount: 0)
After Increment: 1
Hash: 12357890
Queue Hash: 12357890 1
After: (HashCount: 1)
Manual 1: 12357890, 2: (null)
1: 12357890
//////////////////////////////
Before: (HashCount: 1)
1: asdfghjkl <----- This should be "1: 12357890", as the array has not yet been changed
After Increment: 2
Hash: asdfghjkl
Queue Hash: asdfghjkl 2
After: (HashCount: 2)
Manual 1: asdfghjkl, 2: asdfghjkl
1: asdfghjkl <----- This should be "1: 12357890"
2: asdfghjkl
Here is my full code for all relevant functions: https://pastebin.com/41W3n5W2
Any help would be greatly appreciated, thanks!
The symptom described in the question usually indicates that the code isn't making a copy of the string. As a result, every "string" stored in the array is just a pointer to the same input buffer, and therefore every entry in the array appears to be the last string received from the user.
The fix is to make a copy of the string. On a POSIX system, you can use the strdup function to make a copy. The strdup function is essentially a call to malloc following by a call to strcpy, which makes a copy of the string, and returns a pointer to the copy. So if your implementation doesn't support strdup, you can easily write your own function to allocate memory, and copy the string.
Related
I have this csv file contain people contact:
NAME, NUMBER, ADDRESS, EMAIL
Kevin Mahendra, +62 812-XXXX-XXXX, Jln.Anggrek Merah 3, kevinitsnovember#gmail.com
Adwi Lanang, +62 821-XXXX-XXXX, Jln.Ruhui Rahayu, adwilanang#gmail.com
Wasis Sirutama, +62 813-XXXX-XXXX, Jln.Pramuka 6 25, wasisnaruto#gmail.com
Alief Dean, +62 811-XXXX-XXXX, Jln.Padat Karya, aliefdean#gmail.com
Baharudin Nuri, +62 813-XXXX-XXXX, Jln.Ruhui Rahayu 1, baharudin008#yahoo.com
Yonggi Wijaya, +62 853-XXXX-XXXX, Jln.PM Noor Pondok S, yonggiwijaya#gmail.com
Artha Yoga, +62 822-XXXX-XXXX, Jln.A.Yani Gg.1, arthayoga97#gmail.com
Rusydi Nashier, +62 858-XXXX-XXXX, Jln.Perjuangan No.90, rusydinashier#gmail.com
Andre Pieters, +62 822-XXXX-XXXX, Jln.Villa Tamara No.1, azzahz#gmail.com
Paco Corleone, +62 816-XXXX-XXXX, Jln.Anggrek Merah 3, pacocorleone#gmail.com
And this is my C code:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Number of buckets for TABLE
#define N 26
#define MAX 50
typedef struct Contact
{
char name[MAX];
char number[MAX];
char address[MAX];
char email[MAX];
struct Contact* next;
}
Contact;
void searching_contact(FILE *file);
void load_hash_table(FILE **file);
unsigned int hash(const char *name);
// Hash table
Contact *table[N];
int main(void)
{
// OPEN CSV FILE AS append and read mode
FILE *file = fopen("contacts.csv", "r");
if (file == NULL)
{
printf("Error open file!\n");
return 1;
}
searching_contact(file);
fclose(file);
return 0;
}
void searching_contact(FILE *file)
{
char name[MAX];
printf("Search Name: ");
scanf("%[^\n]%*c", name);
fflush(stdin);
// Load csv file into hash table first
load_hash_table(&file);
// Get index number by calling hash function
int hashIndex = hash(name);
printf("\n\nIndex number we get from searching: %i\n", hashIndex);
// Point to table that may contain the person
Contact *cursor = table[hashIndex];
// This will always print the last person
printf("The name on the table: %s\n", cursor->name);
// Keep traversing linked list in table
while (cursor != NULL)
{
// If the person found, print the contact information
if (strcmp(name, cursor->name) == 0)
{
printf("%s %s %s %s", cursor->name, cursor->number, cursor->address, cursor->email);
}
else
{
// If not the person, but in the same table, go to the next linked list
cursor = cursor->next;
}
}
printf("Not found!\n");
}
// FUNCTION TO LOAD CSV FILE INTO HASH TABLE
void load_hash_table(FILE **file)
{
Contact *new = malloc(sizeof(Contact));
if (new == NULL)
exit(1);
/*
"%[^,], "
Empty space or space after above sign will remove spaces or newline (\n) on each string
Just because, when we try to use hash function, the spaces or newline will also include
And we want to remove them so when user searching by name it will produce same hash index
*/
while(fscanf(*file, "%[^,], %[^,], %[^,], %[^\n] ", new->name, new->number, new->address, new->email) == 4)
{
// Skip header from CSV file
if (strcmp("NAME", new->name) == 0)
continue;
// Get index number from hash function with People name as input
int index = hash(new->name);
// Try to print name and it's index in csv file for debugging
printf("%s\n", new->name);
printf("%i\n", index);
/*
Create linked list point to WHAT inside Table[index]
For very first struct, it points to NULL, then store it in Table
Next struct, with the same Index number, it will point the first one
*/
new->next = table[index];
table[index] = new;
// Malloc for next fscanf
Contact *new = malloc(sizeof(Contact));
if (new == NULL)
exit(1);
}
}
// Hash function that will return index number from Table
unsigned int hash(const char *name)
{
// TODO
unsigned long hash = 5381;
int c;
while ((c = toupper(*name++)))
{
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
}
return hash % N;
}
So, Im trying to make a program where user can search people contact by name. And as you can see, i also want to exercise using hash table/hash function. So before searching, it will load all csv file into hash table (see load_hash_table()). But in the end, it always failed to found the person even though user already typing right name.
I try debugging using only printf.
First, at load_hash_table() function i print each name and it's index. And it's working correctly. It prints all the name in csv file and also correct index that produced by hash function.
Second, here's the problem. When i try to print inside searching() function. It produced correct and same index number. But when i print the name, it always print the last person on csv file, which is Paco Corleone. No matter what index number we put in Table[], it always print that last person name.
I don't understand. It seems like the hash table lost all the data that have been loaded before when while loop inside load_hash_table() function is over. Maybe you'll see the problem when you run the code i gave. Please help me, i'm new in C language, thanks!
Couple of problems:
The major problem is that there are two Contact *new = malloc(sizeof(Contact)); lines. One inside the loop and one outside. They are two different variables. The while loop condition is using the one outside the loop. Hence the fscanf is writing to the same memory for every loop. One way to fix that is to make the second instance just new = malloc(sizeof(Contact));. Note that this loop has a memory leak as the last allocated node is lost - left to you as an exercise to fix.
searching_contact has an infinete loop as the if (strcmp(name, cursor->name) == 0) block is missing a break.
in libconfig - is it possible to dymanically enumerate keys?
As an example, in this example config file from their repo - if someone invented more days in the hours section, could the code dynamically enumerate them and print them out?
Looking at the docs, I see lots of code to get a specific string, or list out an array, but I can't find an example where it enumerates the keys of a config section.
Edit
Received some downvotes, so thought I'd have another crack at being more specific.
I'd like to use libconfig to track some state in my application, read in the last known state when the app starts, and write it out again when it exits. My app stores things in a tree (of depth 2) - so this could be niceley represented as an associative array in a libconfig compatible file as below. The point is that the list of Ids (1234/4567) can change. I could track them in another array, but if I could just enumerate the 'keys' in the ids array below - that would be neater.
so
ids = {
"1234" = [1,2,3]
"4567" = [9,10,11,23]
}
e.g (psuedocode)
foreach $key(config_get_keys_under(&configroot)){
config_get_String($key)
}
I can't see anything obvious in the header file.
You can use config_setting_get_elem function to get n-th element of the group, array or list, and then (if it's group) use config_setting_name to get it's name. But AFAIK you can't use digits in key names. So consider following config structure:
ids = (
{
key = "1234";
value = [1, 2, 3];
},
{
key = "4567";
value = [9, 10, 11, 23];
}
);
Then you can easily enumerate through all members of the ids getting the values you want using the following code:
#include <stdio.h>
#include <libconfig.h>
int main(int argc, char **argv) {
struct config_t cfg;
char *file = "config.cfg";
config_init(&cfg);
/* Load the file */
printf("loading [%s]...\n", file);
if (!config_read_file(&cfg, file)) {
printf("failed\n");
return 1;
}
config_setting_t *setting, *member, *array;
setting = config_lookup(&cfg, "ids");
if (setting == NULL) {
printf("no ids\n");
return 2;
}
int n = 0, k, v;
char const *str;
while (1) {
member = config_setting_get_elem(setting, n);
if (member == NULL) {
break;
}
printf("element %d\n", n);
if (config_setting_lookup_string(member, "key", &str)) {
printf(" key = %s\n", str);
}
array = config_setting_get_member(member, "value");
k = 0;
if (array) {
printf(" values = [ ");
while (1) {
if (config_setting_get_elem(array, k) == NULL) {
break;
}
v = config_setting_get_int_elem(array, k);
printf("%s%d", k == 0 ? "" : ", ", v);
++k;
}
printf(" ]\n");
}
++n;
}
printf("done\n");
/* Free the configuration */
config_destroy(&cfg);
return 0;
}
I'm trying to print an array of structs that contain two strings. However my print function does not print more than two indices of the array. I am not sure why because it seems to me that the logic is correct.
This is the main function
const int MAX_LENGTH = 1024;
typedef struct song
{
char songName[MAX_LENGTH];
char artist[MAX_LENGTH];
} Song;
void getStringFromUserInput(char s[], int maxStrLength);
void printMusicLibrary(Song library[], int librarySize);
void printMusicLibraryTitle(void);
void printMusicLibrary (Song library[], int librarySize);
void printMusicLibraryEmpty(void);
int main(void) {
// Announce the start of the program
printf("%s", "Personal Music Library.\n\n");
printf("%s", "Commands are I (insert), S (sort by artist),\n"
"P (print), Q (quit).\n");
char response;
char input[MAX_LENGTH + 1];
int index = 0;
do {
printf("\nCommand?: ");
getStringFromUserInput(input, MAX_LENGTH);
// Response is the first character entered by user.
// Convert to uppercase to simplify later comparisons.
response = toupper(input[0]);
const int MAX_LIBRARY_SIZE = 100;
Song Library[MAX_LIBRARY_SIZE];
if (response == 'I') {
printf("Song name: ");
getStringFromUserInput(Library[index].songName, MAX_LENGTH);
printf("Artist: ");
getStringFromUserInput(Library[index].artist, MAX_LENGTH);
index++;
}
else if (response == 'P') {
// Print the music library.
int firstIndex = 0;
if (Library[firstIndex].songName[firstIndex] == '\0') {
printMusicLibraryEmpty();
} else {
printMusicLibraryTitle();
printMusicLibrary(Library, MAX_LIBRARY_SIZE);
}
This is my printing the library function
// This function will print the music library
void printMusicLibrary (Song library[], int librarySize) {
printf("\n");
bool empty = true;
for (int i = 0; (i < librarySize) && (!empty); i ++) {
empty = false;
if (library[i].songName[i] != '\0') {
printf("%s\n", library[i].songName);
printf("%s\n", library[i].artist);
printf("\n");
} else {
empty = true;
}
}
}
I think the problem is caused due to setting : empty = true outside the for loop and then checking (!empty) which will evaluate to false. What I am surprised by is how is it printing even two indices. You should set empty = false as you are already checking for the first index before the function call.
The logic has two ways to terminate the listing: 1) if the number of entries is reached, or 2) if any entry is empty.
I expect the second condition is stopping the listing before you expect. Probably the array wasn't built as expected (I didn't look at that part), or something is overwriting an early or middle entry.
you gave the definition as:
typedef struct song
{
char songName[MAX_LENGTH];
char artist[MAX_LENGTH];
}Song;
the later, you write if (library[i].songName[i] != '\0') which really seems strange: why would you index the songname string with the same index that the lib?
so I would naturally expect your print function to be:
// This function will print the music library
void printMusicLibrary (Song library[], int librarySize) {
for (int i = 0; i < librarySize; i ++) {
printf("%s\n%s\n\n", library[i].songName,
library[i].artist);
}
}
note that you may skip empty song names by testing library[i].songName[0] != '\0' (pay attention to the 0), but I think it would be better not to add them in the list (does an empty song name make sens?)
(If you decide to fix that, note that you have an other fishy place: if (Library[firstIndex].songName[firstIndex] == '\0') with the same pattern)
I am programming on some device and I encountered
rather strange situation.
The same variable - for the first time has correct value,
but the SAME variable on a different place in code,
has a DIFFERENT value.
What can be causing this? I am pretty sure I didn't modify
the variable in between, I am also pretty sure I didn't
modify the variable using some pointers accidentally.
What can be causing this? I am really confused?
Can it be related that I for example used whole available stack
space of some function - and because of this compiler automatically
0-ifies my variable(or something similar)?
I have some long code inside a single function f.
Here's whole details on usage of pointsEntered variable in my code (how it is used).
/* Let the user input points */
s32 pointsEntered = 0;
int pointsCounter = 0;
while(1)
{
if(pointsCounter == 3)
return; // User entered wrong points 3 times, exit function
bool retStatus = false;
retStatus = inputPoints(&pointsEntered);
if(false == retStatus) // If user didn't enter amount, exit this function
return;
if(pointsEntered>atoi(userAmount)){
PromptBox(false, 0, "Points should not be more\n than current points");
pointsCounter++;
continue;
}
break;
}
// PROBLEM: pointsEntered - is OK here but as it will be shown below, it gets modified down the way
// even though I don't change it directly
char intTest1[50];
sprintf(intTest1, "1pentered %d", pointsEntered); // Here the value is OK! It shows value that I entered, e.g., 220
PromptBox(false, 0, intTest1);
/* Let the user enter 4 digit pin code */
u8 pinEntered[5] = {0};
bool retStatus1 = false;
retStatus1 = inputPin(pinEntered);
pinEntered[5]='\0';
if(false == retStatus1) // If user didn't enter amount, exit this function
return;
char intTest2[50];
sprintf(intTest2, "2pentered %d", pointsEntered); // Here pointsEnetered is OK
PromptBox(false, 0, intTest2);
/* Compute hash of pin code*/
s32 siRet1 = 0;
u8 pinVerify[25]={0};
u8 hashResult[16] = {0};
memcpy(pinVerify,pinEntered,4);
memcpy(&pinVerify[4],"XXXX",21);
siRet1 = sdkMD5(hashResult,pinVerify,25);
char intTest3[50];
sprintf(intTest3, "3pentered %d", pointsEntered); // Here pointsEntered has changed!!!
PromptBox(false, 0, intTest3);
/* convert string hash code to byte array */
unsigned char val[16] = {0};
unsigned char * pos = pinHash;
size_t count = 0;
// WARNING: no sanitization or error-checking whatsoever
for(count = 0; count < sizeof(val)/sizeof(val[0]); count++)
{
sscanf(pos, "%2hhx", &val[count]);
pos += 2 * sizeof(char);
}
char intTest4[50];
sprintf(intTest4, "4pentered %d", pointsEntered);
PromptBox(false, 0, intTest4);
/* Does the pin hash match ? */
if (siRet == SDK_OK && (!memcmp(hashResult,val,16)))
{
MsgBox("PIN OK","",0,SDK_KEY_MASK_ESC | SDK_KEY_MASK_ENTER);
}
else
{
MsgBox("PIN doesn't match-exiting","",0,SDK_KEY_MASK_ESC | SDK_KEY_MASK_ENTER);
return;
}
char intTest[50];
sprintf(intTest, "pentered %d", pointsEntered);
PromptBox(false, 0, intTest);
These two lines may cause it (as it's undefined behavior):
u8 pinEntered[5] = {0};
...
pinEntered[5]='\0';
Here you declare an array of five entries, but then you assign to a sixth item. This will most likely overwrite any previous variable on the stack.
Due to a problem posting, my last question (duplicate of this) was closed.
Background is provided at the end so you can get straight to the problem.
I have a text-based program to help learn vocabulary or anything else (basically simulates flash cards, but flashes up the ones you don't know more often). It seemed to work fine while I was testing it, until I got fed up of the constant backlog of text on the screen, so I implemented a (somewhat unportable) clear screen routine.
Then it started throwing up exceptions, and I put in all sorts of debugging code to try and track it down.
Well... I managed to narrow it down to the following command on line 445:
system("cls");
How can this command cause an exception? Does anyone know a workaround?
I've run this in command prompts on both Windows Vista and Windows 7 with the same result.
Complete source in case anyone wants to compile it themselves or take a look through:
#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;
}
I tried adding the output of stderr on a typical run, but it makes the body too long. Also tried adding it as an answer, but:
Users with less than 100 reputation can't answer their own question for 8 hours after asking. You may self-answer in 7 hours. Until then please use comments, or edit your question instead.
Background: I made my first foray into programming earlier this year, and decided I wanted to start with C before moving on to C++, Java, and perhaps Python and C#.
To get me started in C, after the obligatory hello world, I wrote a small game (text based, also including the "cls" command), and then moved onto this little vocab tester, which was to help me learn Indonesian while I was away in Austria speaking German :-D. I eventually got exasperated at the cls crash and haven't programmed since. I really want to pick it back up, so I'm starting here with this question.
Have you tried printing '\f'? That's the "formfeed" character.
EDIT: I've had a closer look at your code, and there's some stuff going on that I don't like. :-)
For example, in gettextfromkeyboard, if you enter only whitespace, it'll free target, even if that was non-NULL on entry.
In this line:
inputfilename = gettextfromkeyboard(inputfilename,256);
it passes inputfilename, which points to a constant string, into gettextfromkeyboard. Trying to free that is a bad idea.
I also have my doubts about while (getchar()!='\n') getchar();.
Suppose the input is "ABC\n".
The condition will consume and return 'A', the body of the loop will consume 'B', the condition will consume and return 'C', and the body of the loop will consume '\n'.
Try one of the many curses, ncurses, etc. packages around. There should be one somewhere on the web for your version of C, if it is not too uncommon. It should handle all kinds of text screen functionality, including clearing the screen and it is pretty portable.
Possibly system('cls') has been deprecated? Here is another way of doing it.
The code runs fine for me and the CLS command works. Non-reproduceable crashes often indicate memory corruption. I'd say a good place to look first is your readtextfromfile function since it will overwrite the input buffer if a file contains 256 chars.