I need help solving the code below. After the code is compiled using gcc it can be run like ./compiledFile inputFile.txt It should take the inputFile.txt read it while allocating memory dynamically for each variable in this case name and courseID, but my code is not working. The place that I don't understand the most and need help is allocating memory, inserting data into structures and printing the data like the example given below. By the look of this code you could tell that I am a newbie to c and dynamic memory allocation and structure in all.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct people
{
char* name[10];
char* courseID[15];
int grade;
};
void printData(struct people student[], int count);
int main(int argc, char* argv[])
{
FILE *in_file;
char buffer[30];
char *token, *del=",";
int count=0;
struct people student[20];
if(( in_file = fopen(argv[1], "r")) == NULL)
{
printf("unable to open the file");
}
while (fgets(buffer, sizeof(buffer), in_file))
{
student = malloc(sizeof(struct people));
token = strtok(buffer, del);
strcpy(student[count].name, token);
count++;
}
fclose(in_file);
printData(student, count);
}
void printData(struct people student[], int count)
{
int i;
for(i=0; i<count; i++)
{
printf("%s", student[i].courseID);
if (strcmp((student[i].name, student[i].courseID) > 0))
{
printf("%s %s", student[i].name, student[i].grade)
}
}
}
the data.txt file has the following content separated by a comman:
John,MATH 1324,90
David,SCI 1401,88
Omondi,MATH 1324,89
David,MATH 1324,90
when printed out it should look like the following:
MATH 1324
John 90
Omondi 89
David 90
SCI 1401
David 88
first of all, it would be great if you could also share what is the actual output or error you get while running this program.
most of the time dynamic memory allocation is used when we do not know the actual size of data elements, but here you have already fixed the size of struct people student as 20
struct people student[20];
this is absolutely fine, but then you do malloc in while loop
student = malloc(sizeof(struct student);
you have already alloted 20 locations using array declaration, now malloc is not required.
if you want to use dynamic memory allocation using pointers for learning purpose then you should first declare student as pointer to type struct people
struct people* student;
allocate memory dynamically in while loop
student=(struct people*) malloc(sizeof(struct people));
then access it
*(student+count)
hope this helps, if you still have doubts/problems edit the question and include the output/error you get while compiling/running this program.
Several issues with the Question code...
1) Definition of main():
int main(int argc, char* argv[])
It must return an integer. Add a return statement at the end of main(), and make a proper "CLEANUP" section:
printData(student, count);
CLEANUP:
if(in_file)
fclose(in_file);
return(0);
}
2) Better handling of fopen() error condition:
if(( in_file = fopen(argv[1], "r")) == NULL)
{
printf("unable to open the file");
goto CLEANUP;
}
And, initialize the in_file pointer:
int main(int argc, char* argv[])
{
FILE *in_file = NULL;
3) Next, the exact definition of student needs to be established. Is it a static array, or is a pointer to a dynamically allocated array? I will assume that you would like to use a dynamic array (given the question text). However, this assumption conflicts with the following line, which defines student as a static array:
struct people student[20];
Change it to:
struct people *student = NULL;
4) Now, the following question code allocates a new (separate) chunk of memory for each student:
student = malloc(sizeof(struct people));
However, what is needed is all the student records in one array, in the same chunk of memory. So, what is needed is to expand a chunk of memory to include student records as they are read, like this:
while (fgets(buffer, sizeof(buffer), in_file))
{
void *tmp = realloc(student, sizeof(struct people) * (count + 1));
if(NULL == tmp)
{
printf("realloc() failed.\n");
goto CLEANUP;
}
student = tmp;
token = strtok(buffer, del);
5) Take a look at the people structure:
struct people
{
char* name[10];
char* courseID[15];
int grade;
};
It appears that the question code has some difficulty when it comes to pointers & arrays. The code is attempting to define the name and courseID fields as both pointers, and arrays. Given that the question is to do with dynamically allocating stuff, I elect to go that direction. Hence, this structure should be changed to the following:
struct people
{
char *name;
char *courseID;
int grade;
};
6) So, each time through the loop, the student name will be placed in allocated storage, and pointed to by the .name field. So, change this:
token = strtok(buffer, del);
strcpy(student[count]->name, token);
count++;
}
to this:
token = strtok(buffer, del);
student[count].name = strdup(token);
count++;
}
7) I don't understand the intent of this line:
if (strcmp((student[i].name, student[i].courseID) > 0))
I am inclined to eliminate it.
8) The following line has flaws:
printf("%s %s", student[i].name, student[i].grade)
Change it to this to print the integer grade (and don't forget the ending semicolon):
printf("%s %d\n", student[i].name, student[i].grade);
The '\n' makes the output look better, one record per line.
9) Since student is a pointer to dynamically allocated memory (not a static array), change this:
void printData(struct people student[], int count)
to this:
void printData(struct people *student, int count)
10) Now, finish the task of parsing the data; from this:
token = strtok(buffer, del);
strcpy(student[count].name, token);
count++;
}
to this:
token = strtok(buffer, del);
student[count].name = strdup(token);
token = strtok(NULL, del);
student[count].courseID = strdup(token);
token = strtok(NULL, del);
student[count].grade = strtol(token, NULL, 10);
count++;
}
11) Now, to make life easier, sort the array. First by courseID, then by name:
...
count++;
}
/** Sort the array by coursID, then by name. **/
qsort(student, count, sizeof(*student), CmpStudentRecs);
printData(student, count);
...
Which will require an additional "Compare Student Recs" function:
int CmpStudentRecs(const void *recA, const void *recB)
{
int result = 0;
struct people *stuRecA = (struct people *)recA;
struct people *stuRecB = (struct people *)recB;
/** First compare the courseIDs **/
result=strcmp(stuRecA->courseID, stuRecB->courseID);
/** Second (if courseIDs match) compare the names **/
if(!result)
result=strcmp(stuRecA->name, stuRecB->name);
return(result);
}
12) Some finishing touches with the printData() function:
void printData(struct people *student, int count)
{
int i;
char *courseID = "";
for(i=0; i<count; i++)
{
if(strcmp(courseID, student[i].courseID))
{
printf("%s\n", student[i].courseID);
courseID = student[i].courseID;
}
printf("\t%s %d\n", student[i].name, student[i].grade);
}
}
Finished. Output:
SLES11SP2:~/SO> ./test data.txt
MATH 1324
David 90
John 90
Omondi 89
SCI 1401
David 88
SLES11SP2:~/SO>
SPOILER
Change the definition of people to:
struct people
{
char name[10];
char courseID[15];
int grade;
};
This assumes that name won't be longer than 9 characters and coursID won't be longer than 14 characters. If that is not true, change them accordingly.
The line:
student = malloc(sizeof(struct student);
is wrong in couple of ways.
student is already declared to be an array of people. You cannot assign it to point to memory allocated by malloc.
struct student is not a type.
That line can be removed.
The line
strcpy(student[count].name, token);
can cause problems if the length of token is longer than 10 (or whatever size you choose for name in people). The safer thing to do is use strncpy.
strncpy(student[count].name, token, 10);
student[count].name[9] = '\0';
You have not set the value of courseID anywhere. Yet, you are trying to print it in printData. You are doing the same thing for grade. You need to update the processing of input lines to set those correctly.
Change the while loop to:
while (fgets(buffer, sizeof(buffer), in_file))
{
token = strtok(buffer, del);
strncpy(student[count].name, token, 10);
student[count].name[9] = '\0';
token = strtok(NULL, del);
strncpy(student[count].courseID, token, 15);
student[count].courseID[14] = '\0';
token = strtok(NULL, del);
student[count].grade = atoi(token);
count++;
}
There are couple of syntax errors in printData. However, fixing those syntax errors does not take care of your printing requirements. It will be easier to print the data in the order that you want to if you sort the data. The following functions will help you sort the data.
int compareStudent(const void* ptr1, const void* ptr2)
{
struct people* p1 = (struct people*)ptr1;
struct people* p2 = (struct people*)ptr2;
return (strcmp(p1->courseID, p2->courseID));
}
void sortData(struct people student[], int count)
{
qsort(student, count, sizeof(struct people), compareStudent);
}
You can call sortData before calling printData or call sortData first in printData. The logic for printing the data needs to be updated a little bit. Here's an updated printData.
void printData(struct people student[], int count)
{
int i;
int j;
sortData(student, count);
for(i=0; i<count; ++i)
{
printf("%s\n", student[i].courseID);
printf("%s %d\n", student[i].name, student[i].grade);
for ( j = i+1; j < count; ++j )
{
if (strcmp(student[i].courseID, student[j].courseID) == 0)
{
printf("%s %d\n", student[j].name, student[j].grade);
}
else
{
i = j-1;
break;
}
}
}
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct people {
char name[10];//char *name[10] is array of pointer
char courseID[15];
int grade;
};
void printData(struct people student[], int count);
int main(int argc, char* argv[]){
FILE *in_file;
char buffer[30];
char *token, *del=",";
int count=0;
struct people student[20];
if((in_file = fopen(argv[1], "r")) == NULL){
printf("unable to open the file");
return -1;//It is not possible to continue the process
}
while (fgets(buffer, sizeof(buffer), in_file)){
//student = malloc(sizeof(struct people));//It is by securing an array already
token = strtok(buffer, del);
strcpy(student[count].name, token);
token = strtok(NULL, del);
strcpy(student[count].courseID, token);
token = strtok(NULL, del);
student[count].grade = atoi(token);
count++;
}
fclose(in_file);
printData(student, count);
}
int cmp(const void *a, const void *b){
const char *x = ((const struct people*)a)->courseID;
const char *y = ((const struct people*)b)->courseID;
return strcmp(x, y);
}
void printData(struct people student[], int count){
qsort(student, count, sizeof(struct people), cmp);//sort by courseID
char *prev = "";
int i;
for(i=0; i<count; i++){
if(strcmp(prev, student[i].courseID)!=0){
prev = student[i].courseID;
printf("\n%s\n", prev);
}
printf("%-9s %d\n", student[i].name, student[i].grade);
}
}
Related
I don't understand why my string is not copied.
The strings structures are similar to this one "KS 2H 5C JD TD"
Here is my code (the comments are all that I tried (memcpy and strcpy)):
typedef struct Hand Hand;
struct Hand {
char *cards;
double power;
};
Hand* initHand(char *set){
Hand *hand = malloc(sizeof(*hand));
if(hand == NULL)
exit(EXIT_FAILURE);
char card[5][3];
//strcpy(hand->cards,*cards);
int i=0;
char *p = strtok (set, " ");
while (p != NULL)
{
card[i] = p;
printf("%s\n",p);
p = strtok (NULL, " ");
i++;
}
hand->power=0;
}
I was willing to copy every 2 letters in an array with strtok (this is what I wanna do); then I tried to copy the whole string.
Thanks a lot. I understood my mistakes:
Mistakes:
I didn't know that strtok is a destructive function (I was trying to edit a readonly data since I was using a constant).
I didn't allocate memory for char *cards
Solution:
make a copy of the set.
allocate memory.
Here's the code that worked for me:
struct Hand {
char cards[5][3];
double power;
};
Hand* initHand(char *set){
Hand *hand = malloc(sizeof(*hand));
if(hand == NULL)
exit(EXIT_FAILURE);
char copy_set[15]="";
strcpy(copy_set,set);
char **str = (char**) malloc(5*sizeof(char*));
for(int i=0; i<3; i++){
str[i]= (char) malloc(3*sizeof(char));
}
strcpy(hand->cards,str);
int i=0;
char *p = strtok (copy_set, " ");
while (p != NULL)
{
strcpy(hand->cards[i], p);
p = strtok (NULL, " ");
i++;
}
hand->power=0;
return hand;
}
I think that this is reasonably correct code — a relatively simple modification of the code shown in revision 2 of the answer by the OP. There's always room for improvement, of course.
#include <stdlib.h>
#include <string.h>
struct Hand
{
char cards[5][3];
double power;
};
typedef struct Hand Hand;
extern Hand *initHand(const char *set);
Hand *initHand(const char *set)
{
Hand *hand = malloc(sizeof(*hand));
if (hand == NULL)
exit(EXIT_FAILURE);
char copy_set[15] = "";
strcpy(copy_set, set);
int i = 0;
char *p = strtok(copy_set, " ");
while (p != NULL)
{
strcpy(hand->cards[i++], p);
p = strtok(NULL, " ");
}
hand->power = 0;
return hand;
}
#include <stdio.h>
int main(void)
{
Hand *hand = initHand("KS 2H 5C JD TD");
for (int i = 0; i < 5; i++)
printf("Card %d: [%s]\n", i, hand->cards[i]);
printf("Power: %.2f\n", hand->power);
free(hand);
}
As I noted in a comment, the variable str isn't needed. With the revised structure, there is no need to allocate extra cards. I've marked the string input as const. Arguably, it would be better to do a dynamic memory allocation for copy_set (remembering to release the copy too), or at least check that the given data will fit in the allocated space. It would probably be better to print an error message before unilaterally exiting on a memory allocation failure. It is good to check for the failure. It is not good to exit with saying why. It would probably be better to return NULL on failure, and then let the calling code detect and handle the problem.
There's a simple test program attached to the code too.
Be cautious about using strtok(); it is dangerous to use it in library functions. You should generally use strtok_r() (POSIX) or strtok_s() (Microsoft) for preference.
Here is my problem: I have to make this program for school and I spent the last hour debugging and googling and haven't found an answer.
I have an array of structures in my main and I want to give that array to my function seteverythingup (by call by reference) because in this function a string I read from a file is split up, and I want to write it into the structure but I always get a SIGSEV error when strcpy with the struct array.
This is my main:
int main(int argc, char *argv[])
{
FILE* datei;
int size = 10;
int used = 0;
char line[1000];
struct raeume *arr = (raeume *) malloc(size * sizeof(raeume*));
if(arr == NULL){
return 0;
}
if(argc < 2){
return 0;
}
datei = fopen(argv[1], "rt");
if(datei == NULL){
return 0;
}
fgets(line,sizeof(line),datei);
while(fgets(line,sizeof(line),datei)){
int l = strlen(line);
if(line[l-1] == '\n'){
line[l-1] = '\0';
}
seteverythingup(&line,arr,size,&used);
}
ausgabeunsortiert(arr,size);
fclose(datei);
return 0;
}
and this is my function:
void seteverythingup(char line[],struct raeume *arr[], int size,int used)
{
char *token,raumnummer[5],klasse[6];
int tische = 0;
const char c[2] = ";";
int i=0;
token = strtok(line, c);
strcpy(raumnummer,token);
while(token != NULL )
{
token = strtok(NULL, c);
if(i==0){
strcpy(klasse,token);
}else if(i==1){
sscanf(token,"%d",&tische);
}
i++;
}
managesize(&arr[size],&size,used);
strcpy(arr[used]->number,raumnummer);
strcpy(arr[used]->klasse,klasse);
arr[used]->tische = tische;
used++;
}
Edit: Since there is more confusion I wrote a short program that works out the part you are having trouble with.
#include <cstdlib>
struct raeume {
int foo;
int bar;
};
void seteverythingup(struct raeume *arr, size_t len) {
for (size_t i = 0; i < len; ++i) {
arr[i].foo = 42;
arr[i].bar = 53;
}
}
int main() {
const size_t size = 10;
struct raeume *arr = (struct raeume*) malloc(size * sizeof(struct raeume));
seteverythingup(arr, size);
return 0;
}
So basically the signature of your functions is somewhat odd. Malloc returns you a pointer to a memory location. So you really dont need a pointer to an array. Just pass the function the pointer you got from malloc and the function will be able to manipulate that region.
Original Answer:
malloc(size * sizeof(raeume*));
This is probably the part of the code that gives you a hard time. sizeof returns the size of a type. You ask sizeof how many bytes a pointer to you raeume struct requires. what you probably wanted to do is ask for the size of the struct itself and allocate size times space for that. So the correct call to malloc would be:
malloc(size * sizeof(struct raeume));
I'm new to C, so this may be a silly question to ask:
What I want to do here is to input the data to the array of pointers to a structure and then print it out. But I get a segmentation fault when running into the insert function.
Below is my code
common.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct book * Book;
struct book{
int id;
char *name;
};
extern int b_insert(Book *b, int id, char *name);
extern int b_print(Book books[], int len);
insert.c
#include "common.h"
int b_insert(Book *b, int id, char *name){
Book p;
p = (Book)malloc(sizeof(struct book));
p->id = id;
strcpy(p->name, name);
*b = p;
printf("success insert book:\n");
printf("\tID: %d Name: %s\n", (*b)->id, (*b)->name);
return 0;
}
int b_print(Book books[], int len){
int i;
printf("Book List\n");
for(i=0; i<len; i++){
printf("books[%d] = ID: %d, Name: %s\n", i, books[i]->id, books[i]->name);
}
return 0;
}
main.c
#include "common.h"
#define MAX 2
int main(){
Book books[MAX];
Book *b=books;
int i;
int id;
char name[10];
for(i=0; i<MAX; i++){
printf("please input new books info\n");
printf("ID: ");
scanf("%d", &id);
printf("Name: ");
scanf("%s", name);
if(b_insert(b, id, name) == -1){
printf("fail to insert\n");
}
b++;
}
b_print(books, MAX);
return 0;
}
Main problem:
Allocate memory for p->name before using
strcpy(p->name, name);
using malloc:
p->name = malloc(10); //Or some other size
Other problems:
Remove the cast here:
p = (Book)malloc(sizeof(struct book));
Why? Here is the answer
if(b_insert(b, id, name) == -1){ will never be true.
Check the result of malloc to check if it was successful in allocating memory.
Check the return value of all the scanfs to see if it was successful in scanning data.
Add a length modifier to the second scanf to prevent buffer overflows:
scanf("%9s", name); /* +1 for the NUL-terminator */
You're not allocating space for name:
int b_insert(Book *b, int id, char *name){
Book p;
p = malloc(sizeof(struct book));
if (p != NULL)
{
p->name = malloc(strlen(name)+1); // It allocates space where the input name will be copied.
if (p->name != NULL)
{
p->id = id;
strcpy(p->name, name);
*b = p;
printf("success insert book:\n");
printf("\tID: %d Name: %s\n", (*b)->id, (*b)->name);
}
else return -1; // No space to allocate string
}
else return -1; // No space to allocate struct
return 0;
}
As mentioned before, allocate space for p->name. You should probably also use something different to read the book title, either scanf format %ms with a pointer to a char pointer, or %9s with your buffer, otherwise the title "war or peace" will also result in a segfault.
Here you create a static variable and the space for it is allocated automatically.
Book p;
You can allocate a space manually when you assign it to pointer, in this line it's not pointer but static variable.
p = (Book)malloc(sizeof(struct book));
What's more if you want to refer to attribute of static variable you should use "." instead of "->". So you have two option. Create a pointer and allocate a space for the structure and then you "->" oraz create static variable.
p->id = id;
The problem I want to solve is: Count the number of occurrences of key words in a C code. Here's the code:
The problem is that I get a segmentation fault. In my code I mentioned where
is the problem. Can you, please, explain me why ?
In header:
struct Chei
{
char *cuv;
int contor;
};
typedef struct Chei chei;
int ReadCode(char *a[]);
void determine(chei *Ch, char *temp, int size);
void Frequency(chei *Ch, int nr_lines, char *a[], int size);
In main:
chei tab_chei[] = {{"while", 0},{"if", 0}, {"case", 0}, {"switch", 0}};
int size = sizeof(tab_chei)/sizeof(tab_chei[0]);
char *Code[MaxL];
int nr_lines;
nr_lines = ReadCode(Code);//number of lines of text
Frequency(tab_chei, nr_lines, Code, size);
In function file:
I think there is no problem with reading text (function ReadCode() -- here I allocated memory for each Code[i] using malloc). I used an array of pointers to char for this.
// This functions determines if the word "temp" is a keyword, and increases
//"contor" if it is.
void determine(chei *Ch, char *temp, int size)
{
int i;
for (i = 0; i < size; ++i)
{
if (!strcmp(Ch[i].cuv, temp))
{
Ch[i].contor++;
break;
}
}
}
Array "a" contains the text.
void Frequency(chei *Ch, int nr_lines, char *a[], int size)
{
int i;
char temp[MaxCh];
char *token = 0;
strcpy(temp, a[0]);//I put a[0] as a particular case
token = strtok(temp, " ");
determine(Ch, token, size);
while (token != NULL)
{
token = strtok(NULL, " ");
determine(ch, token, size); //here is the problem.
//I observed that if I delete this line, there is no error
//but still it isn't what I want to get
}
for (i = 0; i < size; ++i)
{
printf("\n%-10s%-4d", Ch[i].cuv, Ch[i].contor);
}
}
token = strtok(NULL, " ");
determine(ch, token, size); //here is the problem.
You're not checking token before passing it to determine(). The strcmp() call is undefined behaviour when given a null pointer.
I think the problem is that you did not allocate memory for each of the string the pointer will point to, char *Code[MaxL];.
you only allocated memory for the pointers, you need to do something like
Code[0] = calloc(0, 100);
.
In this program, there is a segmentation fault. The program can print out "loop end" successfully and segmentation fault appeared after "loop end", which means there is not error in read_name function. But I could not figure out any error in my free_memory function. Could anyone help me figure out? Thank you.
input file:
9
Clinton, Hillary R.
Bonds, Bobby S.
Bonds, Barry L.
Clinton, William I.
Clinton, Chelsea T.
Bush, Laura M.
Bush, George W.
Bush, Jenna F.
Bush, Barbara G.
program:
#include <stdio.h>
#include <malloc.h>
#include<string.h>
void alloc(char ***surname, char ***first, char **mid_init, int num);
void read_names(FILE *inp, char **surname, char **first, char *mid_init, int num );
void free_memory(char **surname, char **first, char *mid_init, int num);
int main(int argc, char *argv[])
{
int num = 0;
char **surname, **first, *mid_init;
FILE *inp = fopen(argv[1], "r");
FILE *outp = fopen(argv[2], "w");
char array[79];
fscanf(inp, "%d", &num);
printf("%d\n", num);
fgets(array, 79, inp);
alloc(&surname, &first, &mid_init, num);
read_names(inp, surname, first, mid_init, num);
free_memory(surname, first, mid_init, num);
fclose(inp);
fclose(outp);
return 0;
}
void alloc(char ***surname, char ***first, char **mid_init, int num)
{
int i;
*surname = (char**)malloc(num * sizeof(char*));
*first = (char**)malloc(num * sizeof(char*));
*mid_init = (char*)malloc(num * sizeof(char));
for(i=0; i<num; i++)
{
(*surname)[i] = (char*)malloc(15*sizeof(char));
(*first)[i] = (char*)malloc(10*sizeof(char));
}
}
void read_names(FILE *inp, char **surname, char **first, char *mid_init, int num )
{
char *token, array[79];
char delim[6] = ", .\n";
int i=0;
fgets(array, 79, inp);
printf("loop begins\n");
for(i=0; i<num; i++)
{
fgets(array, 79, inp);
printf("%s", array);
token = strtok(array, delim);
strcpy( (surname[i]), token);
printf("%s ", (surname[i]));
token = strtok(NULL, delim);
strcpy( (first[i]), token);
printf("%s ", (first[i]));
token = strtok(NULL, delim);
*mid_init = token[0];
printf("%s\n", mid_init);
printf("\n\n");
}
printf("\nloop ends\n");
}
void free_memory(char **surname, char **first, char *mid_init, int num)
{
int i;
for(i=0;i<num;i++)
{
free((surname)[i]);
free((first)[i]);
}
free(surname);
free(first);
free((mid_init));
}
First off, you're limiting yourself to 14-character first names and 9-character last names, so that would be the first thing I'd check, that your names aren't longer than this.
If they are, you'll probably corrupt the memory arena when copying them.
One way to check this is to simply print the length of token every time you set it, such as:
token = strtok(array, delim);
printf ("DEBUG: token length is %d\n", strlen (token));
Keep in mind that corruption does not necessarily have a visible immediately or even ever. In this case, what's most likely happened is that you've overwritten a vital piece of inline control information in the memory arena, such as a memory block size or a pointer to another memory block.
However, there's no code actively checking for that when you write to memory so it's probably only found when you next try to do a memory allocation or de-allocation call.
Your next call like this after the corruption is your free calls and that's almost certainly where it's being found, because the arena is corrupt.
Bottom line, writing beyond the end of allocated memory is undefined behaviour. That means you shouldn't do it.
If it turns out your names aren't too long (as you state in a comment), you need to then ask yourself why you have a superfluous fgets(array, 79, inp); in your code. I understand why it's needed in main so as to move to the next line after inputting the line count with a call to fscanf. And that one does its job well.
However, you have another one at the start of read_names which effectively throws away the first name in your list. That's going to cause problems because, while your code thinks there are X names in the file, you've thrown away the first one meaning that there are only X - 1 remaining. You can tell this because, when you begin to print out the names, the first one from the file appears to be missing.
If you remove the fgets at the start of read_names, you should find it's okay.
As an aside, there's a couple of other changes I'd make to the code. First you really should check all those malloc calls in case one of them fails. That's the general rule for all functions that can fail when you rely later on them not having failed.
Second, I'm not really a big fan of ever multiplying by sizeof(char) - this is guaranteed by the standard to always be 1, so multiplying by it clogs up the code and makes it less readable.
Try replacing
token = strtok(NULL, delim);
*mid_init = token[0];
printf("%s\n", mid_init);
with
token = strtok(NULL, delim);
mid_init[i] = token[0];
printf("%c\n", mid_init[i]);
When mid_init memory chunk is filled with garbages without any null in it, 'printf("%s\n", mid_init);' might read beyond the data segment causing segmentation fault.
But #paxdiablo's answer has a much better chance to be the case here.
#Bruce, segmentation fault doesn't always appear at the exact spot it happened.
I don't know why you are getting a segmentation fault, but if I was writing this, I would try and make it a bit simpler (I don't think you are doing yourself any favors) - it makes no sense to pass around something like char ***surname.
This is only my personal opinion, but I would do something like this:
#include <stdio.h>
#include <malloc.h>
typedef struct {
char **data;
unsigned int count;
unsigned int actualSize;
} StringArray;
void StringArray_init(StringArray *array)
{
array->count = 0;
array->actualSize = 0;
}
void StringArray_add(StringArray *array, char* value)
{
if (array->actualSize == 0)
{
array->data = (char**)malloc(sizeof(char*)* 4);
array->actualSize = 4;
}
else
{
if (array->count >= array->actualSize)
{
array->actualSize *= 2;
array->data = (char**)realloc(array->data,sizeof(char*) * array->actualSize);
}
}
array->data[array->count] = value;
array->count++;
}
char* StringArray_get(StringArray *array, unsigned int position)
{
if (position < array->count)
return array->data[position];
else
return 0;
}
void StringArray_clear(StringArray *array)
{
if (array->count >0) free(array->data);
array->count = 0;
array->actualSize = 0;
}
int main ()
{
StringArray surname;
StringArray_init(&surname);
StringArray_add(&surname, "Smith");
StringArray_add(&surname, "Jones");
for(int i=0;i<surname.count;i++)
{
printf("%s\n", StringArray_get(&surname,i));
}
StringArray_clear(&surname);
}
What the above code is doing is allocating memory when you add a value, but instead of allocating space for one item, it adds enough for four. If you get to the point where you add a fifth, it will then double the space to 8 items. This method should help with memory fragmentation. I'm also using a structure, which just makes it a bit easier to pass this array around.
I could also do something like this if I wanted to allocate memory for the strings (just include the string.h header):
int main ()
{
StringArray surname;
StringArray_init(&surname);
char *name = (char*)malloc(sizeof(char) * 6);
strcpy(name,"Smith");
StringArray_add(&surname, name);
name = (char*)malloc(sizeof(char) * 6);
strcpy(name,"Jones");
StringArray_add(&surname, name);
for(int i=0;i<surname.count;i++)
{
printf("%s\n", StringArray_get(&surname,i));
}
// Free memory
for(int i=0;i<surname.count;i++)
{
char *name = StringArray_get(&surname,i);
if (name != NULL) free(name);
}
StringArray_clear(&surname);
}
The size of each name is 6 because there are 5 characters and an extra one for 0, which is the string terminator.
Sorry if this doesn't directly answer your question, but hope it helps.
Bruce,
In your data file, you need a blank line between the number and list of names because of the first fgets() at the beginning of read_names() consumes the second line.
Because the program skipped the second line, it could read only 8 names and the last line read was a blank line, which caused strtok to return a null and the next strcpy tried to read from address 0, which is, of course, a segmentation fault.
And in my machine, the fault happened before printing "loop ends".
You need to check the return values of function calls (strtok in this case) for possible errors. Otherwise you will be wasting a lot of time debugging like this.