Jim S.
asked 03/25/22Add file i/o to your program
Implement a function writeEmpToFile that takes two arguments: a struct Employee pointer and a FILE *.
It should write each field in order as an appropriate type. Note that you will probably want to write a length of the name before you write the characters of the name. By doing this, when you write the load function below, you can read the length of the string and use it to malloc a buffer of the proper size to hold the name.
Because of the embedded name pointer, you CAN NOT write the Employee struct as a single struct. You will need to write it out field by field. You will want to write this as a binary file, not as a text file.
Implement a SAVE command in your main loop that will save all the employees out to a file. The SAVE command should ask for a file name, similar to the way your FIND command asked for a name.
Implement a function readEmpFromFile that takes a FILE * as the only argument and returns a pointer to a struct employee. This function should read the information from the file (reversing what writeEmpToFile does), create a new employee and fill in the data. If the FILE has no more info (is at end of file), this function should return NULL. This function must also ensure any employee actually created (not the NULL) is added to the array (as is done in createEmployee).
Change main to load employees from a file if a command line argument is given to the program (using your readEmpFromFile function you just wrote).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define EMPS_SIZE 20
#define SSN_SIZE 9
#define MAX_EMPS 19
typedef struct {
int salary;
int yearBorn;
char ssn[SSN_SIZE];
char * name;
}
Employee;
Employee * emps[EMPS_SIZE];
int totalEmps = 0;
void displayEmployee(Employee * person) {
printf("\nYou entered the name: %s\nYou entered the year: %d\nYou entered the social security number: ", person -> name, person -> yearBorn);
for (int i = 0; i < SSN_SIZE; i++) {
if (i == 2) {
printf("%c-", person -> ssn[i]);
} else if (i == 4) {
printf("%c-", person -> ssn[i]);
} else if (i == 9) {
printf("%c ", person -> ssn[i]);
} else {
printf("%c", person -> ssn[i]);
}
}
int num = person -> salary / 1000;
int num2 = person -> salary % 1000;
printf("\nYou entered the salary: $%d,%03d\n\n\n", num, num2);
}
void readEmployee(Employee * person) {
int * inputYearBorn = & person -> yearBorn;
int * inputSalary = & person -> salary;
char buff[256];
printf("Welcome to the add employee manager!\n\n");
printf("Enter the Employees name: ");
scanf("%s", buff);
person -> name = malloc(strlen(buff + 1));
strcpy(person -> name, buff);
printf("Enter the year the Employee was born: ");
scanf("%d", inputYearBorn);
printf("Enter the salary the Employee makes: ");
scanf("%d", inputSalary);
printf("Enter the ssn of the Employee: ");
scanf("%s", buff);
* person -> ssn = '\0';
strncat(person -> ssn, buff, 9);
}
void createEmployee(void) {
if (totalEmps <= MAX_EMPS) {
for (int i = 0; i <= MAX_EMPS; ++i) {
if (emps[i] == NULL) {
Employee * newEmployee = (Employee * ) malloc(sizeof(Employee));
emps[i] = newEmployee;
totalEmps++;
readEmployee(newEmployee);
break;
}
}
} else {
printf("You cannot hire anymore employees\n\n");
}
}
void releaseEmployee(Employee * person) {
free(person -> name);
free(person);
}
void listEmployees(void) {
for (int i = 0; i <= MAX_EMPS; i++) {
if (emps[i] == NULL) {
printf("No employee at : %d\n", i);
} else {
printf("Employee position in list : %d\n", i);
displayEmployee(emps[i]);
}
}
printf("Thats all the employees!\n\n");
}
Employee * findEmployee(char * name) {
printf("You entered the name: %s\n\n", name);
int counter = 0;
for (int i = 0; i < totalEmps; i++) {
if (!strcmp(emps[i] -> name, name)) {
printf("%s is an employee! They are at: %d\n", name, i);
return emps[i];
} else {
counter++;
}
}
printf("This employee doesn't exist\n");
return NULL;
}
void fireEmployee(char * name) {
printf("You entered the name: %s\n\n", name);
int counter = 0;
for (int i = 0; i < totalEmps; i++) {
if (!strcmp(emps[i] -> name, name)) {
printf("%s is now fired! They were at: %d\n", name, i);
free(emps[i]);
emps[0] = NULL;
return;
} else {
counter++;
}
}
printf("This employee doesn't exist\n");
return;
}
int salaryComp(Employee ** a, Employee ** b) {
Employee * empA = * a;
Employee * empB = * b;
if ( & empA -> salary == NULL && & empB -> salary != NULL) {
printf("I got here 2\n\n");
return 1;
} else if ( & empB -> salary == NULL && & empA -> salary != NULL) {
printf("I got here 3\n\n");
return -1;
} else if ( & empA -> salary == NULL && & empB -> salary == NULL) {
printf("I got here 4\n\n");
return -1;
} else {
printf("empA salary == %d\n", empA -> salary);
printf("empB salary == %d\n", empB -> salary);
return (empB -> salary - empA -> salary);
}
}
int nameComp(Employee ** a, Employee ** b) {
Employee * empA = * a;
Employee * empB = * b;
if (empA == NULL && empB != NULL) {
return 1;
} else if (empB == NULL && empA != NULL) {
return -1;
} else if (empA == NULL && empB == NULL) {
return -1;
} else {
return 0;
}
}
void sort(Employee * base[], int n, int( * compareFunc)(Employee ** , Employee ** )) {
qsort((void ** ) base, n, sizeof(void * ), (int( * )(const void * ,
const void * )) compareFunc);
}
void main(void) {
int end = 1;
char * command;
char buff[256];
char * findEmp;
char findBuff[256];
char * sortType;
char sortBuff[256];
char hire[] = "HIRE";
char list[] = "LIST";
char quit[] = "QUIT";
char find[] = "FIND";
char fire[] = "FIRE";
char _sort[] = "SORT";
char salary[] = "SALARY";
char name[] = "NAME";
printf("Welcome to the Employee Manager dashboard!\n");
while (end) {
printf("Would you like to HIRE, LIST, FIND, FIRE, SORT, or QUIT?\n\n");
scanf("%s", buff);
command = malloc(strlen(buff + 1));
strcpy(command, buff);
printf("You entered the command: %s\n\n", command);
if (!strcmp(command, hire)) {
createEmployee();
} else if (!strcmp(command, list)) {
listEmployees();
} else if (!strcmp(command, find)) {
printf("Enter an employee name, to find if they are a current employee:\n\n");
scanf("%s", findBuff);
findEmp = malloc(strlen(findBuff + 1));
strcpy(findEmp, findBuff);
findEmployee(findEmp);
} else if (!strcmp(command, fire)) {
printf("Enter an employee name, to fire\n\n");
scanf("%s", findBuff);
findEmp = malloc(strlen(findBuff + 1));
strcpy(findEmp, findBuff);
fireEmployee(findEmp);
} else if (!strcmp(command, _sort)) {
printf("Would you like to sort by SALARY or NAME?\n\n");
scanf("%s", sortBuff);
sortType = malloc(strlen(sortBuff + 1));
strcpy(sortType, sortBuff);
if (!strcmp(sortType, name)) {
printf("sorting by name\n\n");
sort(emps, EMPS_SIZE, nameComp);
listEmployees();
} else if (!strcmp(sortType, salary)) {
printf("sorting by salary\n\n");
sort(emps, EMPS_SIZE, salaryComp);
listEmployees();
} else {
printf("Improper sort type entered, exiting to main menu\n\n");
}
} else if (!strcmp(command, quit)) {
printf("Quitting the managers dashboard!\n\n");
end = 0;
} else {
printf("Please enter a proper command\n\n");
}
}
}
void writeEmpToFile(Employee *emp, FILE *f) {
fwrite(&emp->salary, sizeof(emp->salary), 1, f);
fwrite(&emp->yearBorn, sizeof(emp->yearBorn), 1, f);
fwrite(emp->ssn, sizeof(char), SSN_SIZE, f);
int len = strlen(emp->name) + 1;
fwrite(&len, sizeof(int), 1, f);
fwrite(emp->name, sizeof(char), len, f);
}
1 Expert Answer
Dayaan M. answered 18d
Computer Science Graduate with Computer Engineering Coursework
The assignment is really asking you to design a small serialization format, and once you decide what the bytes on disk look like, both functions almost write themselves. Let me go through why the struct cannot be dumped directly, then give you working code, then flag two bugs I noticed in what you posted.
Why fwrite of the whole struct fails. Your struct is
typedef struct { int salary; int yearBorn; char ssn[SSN_SIZE]; char *name; } Employee;
I compiled that and printed sizeof(Employee). It came out to 32 bytes, even though the data you care about is 4 + 4 + 9 = 17 bytes plus whatever the name string holds. The extra bytes are padding the compiler inserts so the pointer lands on an 8 byte boundary. More importantly, 8 of those 32 bytes are the pointer itself, which is just an address in this run of this process. Write it to disk and read it back tomorrow and it points at nothing. That is exactly what your instructor means by "you can not write the Employee struct as a single struct": the name is not in the struct, only a signpost to it is.
So you write the fields one at a time, and for the name you write a length first and then that many characters. Writing the length first is what makes reading possible, because otherwise the reader has no idea where the name ends.
One subtlety that bites people here. SSN_SIZE is 9 and ssn is char[9] holding nine digits, so there is no room for a terminating null. It is a character array, not a C string. Always move it with exactly SSN_SIZE bytes and never with strcpy, strlen, or %s. Your displayEmployee already loops character by character, which is correct; just keep that discipline in the file code.
The writer.
void writeEmpToFile(Employee *person, FILE *fp) {
int len = (int)strlen(person->name);
fwrite(&person->salary, sizeof(int), 1, fp);
fwrite(&person->yearBorn, sizeof(int), 1, fp);
fwrite(person->ssn, sizeof(char), SSN_SIZE, fp);
fwrite(&len, sizeof(int), 1, fp);
fwrite(person->name, sizeof(char), len, fp);
}
Note the & on the two ints and on len, because fwrite wants an address, but no & on ssn or name, because an array name and a char pointer already are addresses. That asymmetry is probably the single most common compile error on this assignment.
The reader. This is the mirror image, in the same order, and it is where the error handling matters.
Employee *readEmpFromFile(FILE *fp) {
Employee *p = malloc(sizeof(Employee));
if (p == NULL) return NULL;
int len;
if (fread(&p->salary, sizeof(int), 1, fp) != 1) { free(p); return NULL; }
if (fread(&p->yearBorn, sizeof(int), 1, fp) != 1) { free(p); return NULL; }
if (fread(p->ssn, sizeof(char), SSN_SIZE, fp) != SSN_SIZE) { free(p); return NULL; }
if (fread(&len, sizeof(int), 1, fp) != 1) { free(p); return NULL; }
p->name = malloc(len + 1);
if (p->name == NULL) { free(p); return NULL; }
if (fread(p->name, sizeof(char), len, fp) != (size_t)len) { free(p->name); free(p); return NULL; }
p->name[len] = '\0';
if (totalEmps < MAX_EMPS) emps[totalEmps++] = p;
return p;
}
Three things to notice. First, the question says to return NULL at end of file, and checking the return value of fread is how you detect that cleanly; the first fread simply comes up short and you bail. Second, malloc(len + 1) and then setting p->name[len] = '\0' is what turns the raw characters back into a usable C string, since you deliberately did not store the null byte. Third, free the partial allocations before returning NULL, or every truncated file leaks memory.
I actually ran this. I compiled it with gcc -Wall -Wextra, wrote five records covering the awkward cases (salary 0, salary 999, a two character name, a nineteen character name, and INT_MAX), read them all back, and compared every field. All five round-tripped exactly, and valgrind reported no errors. The reason I picked those particular salaries will become clear in a second.
Opening the files. Use "wb" and "rb", not "w" and "r". On Linux and macOS it makes no difference, but on Windows text mode will silently translate byte 0x0A into two bytes, which corrupts any integer or name that happens to contain it. Since the assignment explicitly says to write this as a binary file, use the b.
Hooking up SAVE and the command line argument. SAVE just asks for a filename the same way FIND asks for a name, then loops:
FILE *fp = fopen(filename, "wb");
if (fp == NULL) { printf("Could not open %s\n", filename); }
else { for (int i = 0; i < totalEmps; i++) writeEmpToFile(emps[i], fp); fclose(fp); }
And in main, if argc > 1 then open argv[1] with "rb" and call readEmpFromFile in a while loop until it returns NULL. Since readEmpFromFile already appends to emps and bumps totalEmps, the loop body can be empty.
Two bugs in the code you posted, unrelated to file I/O.
The first is in how displayEmployee prints the salary:
int num = person->salary / 1000;
int num2 = person->salary % 1000;
printf("\nYou entered the salary: $%d,%03d\n\n", num, num2);
That is fine for salaries of 1000 or more, but it misbehaves below that. I ran it: salary 999 prints as $0,999 and salary 0 prints as $0,000. The fix is to special case it, printf("$%d", salary) when salary < 1000 and the existing version otherwise. It also only handles up to six figures; a seven figure salary prints as $1234,567 with the comma in one place instead of two. If you want it fully general, write a small helper that formats the number recursively in groups of three.
The second is a naming mismatch worth checking against your spec: EMPS_SIZE is 20 but MAX_EMPS is 19, and the array is declared with EMPS_SIZE. If MAX_EMPS is meant to be the largest valid index, then comparing totalEmps < MAX_EMPS stops you one slot early and you only ever store 19 employees in a 20 slot array. Decide which one is the count and which is the last index, and use that one consistently in createEmployee and in readEmpFromFile, otherwise the two paths can disagree about when the array is full.
Last thought on design, since this is the part that transfers to future work: the format above is a length prefixed binary record, and it is the same idea behind essentially every real serialization scheme. It is fast and compact, but it is tied to your machine's integer size and byte order, so a file written on one machine may not read correctly on another. If that ever matters, write each integer as text, or convert to a fixed width type and a fixed byte order before writing. For this assignment the simple version is what is being asked for, but it is worth knowing which assumption you are making.
Still looking for help? Get the right answer, fast.
Get a free answer to a quick problem.
Most questions answered within 4 hours.
OR
Choose an expert and meet online. No packages or subscriptions, pay only for the time you need.
Inactive Tutor
You know, it feels really disrespectful to post the question again verbatim, but with my partial answer appended to it to make it appear like it's part of your work. I love helping people who want to learn, but It feels like you're not interested in learning how to do this, but you just want someone else to do your work for you. What things have you tried? What problems are you running into? What specific questions do you have?03/26/22