3

I have a bunch of practice questions in Structures, all of which involve structures passed as pointers to function arguments. Now, I have a specific question at hand, which asks me to store some names and telephone numbers in a phone book structure. It reads as follows:

Write a C program that implements the following two functions. The function readin() reads a number of persons’ names and their corresponding telephone numbers, passes the data to the caller via the parameter p, and returns the number of names that have entered. The character # is used to indicate the end of user input. The function search() finds the telephone number of an input name target, and then prints the name and telephone number on the screen. If the input name cannot be found, then it will print an appropriate error message. The prototypes of the two functions are given below:

 int readin(PhoneBk *p);
 void search(PhoneBk *p, int size, char *target);

The structure definition for PhoneBk is given below:

typedef struct {
    char name[20];
    char telno[20];
} PhoneBk;

The program template is as follows:

#include <stdio.h>
#include <string.h>

#define MAX 100

typedef struct {
    char name[20];
    char telno[20];
} PhoneBk;

int readin(PhoneBk *p);
void search(PhoneBk *p, int size, char *target); 

int main() {
    PhoneBk s[MAX];
    char t[20];
    int size;

    size = readin(s);
    printf("Enter search name: \n");

    gets(t);
    search(s, size, t);

    return 0;
}

int readin(PhoneBk *p) {
    /* Write your program code here */
}

void search(PhoneBk *p, int size, char *target) {
    /* Write your program code here */ 
}

I'll have to compare strings twice in my program: one, to check if # is entered as the name (in which case I'd like the program to jump straight to the search function without asking the phone number); and two, if the targeted name matches that in one of the phone book structures.

Now I am completely alien to the concept of structures being declared as arrays but passed as pointers, so I think the way to get the user input for the next record is by incrementing the pointer variable (here p). For the get-the-records-until-#-is-entered function, I wrote this (using strcmp):

int readin(PhoneBk *p) {
    strcpy(p->name, "Test");    //To guarantee a character that is not #.
    while (strcmp(p->name, "#") != 0) {
        printf("Enter person name, # to stop.\n");
        scanf("%s", p->name);
        printf("Enter the telephone number.\n");
        scanf("%s", p->telno);
        p++;
    }
    return p;
}

My intention is to typecast p->name as a string so that strcmp() can work on it. But a warning is repeatedly shown:

warning: cast from pointer to integer of different size
warning: passing argument 1 of strcmp() makes pointer from integer without a cast

This error has been discussed many times on the forums (I searched), but nothing that I've seen really helps with this combination of structures and strcmp().

I gave up on strcmp() entirely for the second function, the one that displays the record of a person if the targeted name is found, and so I used a loop to compare the strings directly.

void search(PhoneBk *p, int size, char *target) {
    int i, flag = 1, strCompare;
    char c1 = *(p->name), c2 = *target, p1 = p->name, p2 = target;
    while (c1 != '\0' && c2 != '\0') {  //Comparing strings.
        strCompare = (int)(c1 - c2);
        if (strCompare != 0)
            break;
        else {
            p1++;
            p2++;
        }
    }
    for (i = 0; i < size; i++, p++) {
        if (strCompare == 0) {
            flag = 0;
            printf("Name = %s, Tel. = %s\n", p->name, p->telno);
        }
        if (flag == 1)
            printf("Name not found.\n");
    }
}

But again I get a number of warnings.

warning: cast from pointer to integer of different size
warning: initialization makes pointer from integer without a cast

I really think my life would be easier if the structures were all passed as arrays, in which case I could simply iterate through the array of structures using a For loop and be done with it. This is what I've always done throughout high school, but now that I'm in university, I am forced to deal with structures passed as pointers.

How do I handle strcmp() in this case? How can this be applied to any program with structures passed as pointers?

  • 5
    Long question for a simple syntax error. Change `strcmp((char)p->name, "#")` to `strcmp(p->name, "#")`. A single `char` is not a string, hence the warning. – Lundin Mar 06 '19 at 08:33
  • 4
    ***Never ever*** use `gets`! It's [a dangerous function](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-so-dangerous-that-it-should-not-be-used) that even have been removed form the C standard. Use e.g. [`fgets`](https://en.cppreference.com/w/c/io/fgets) instead, but be aware of its differences from `gets`. – Some programmer dude Mar 06 '19 at 08:33
  • 1
    Actually, the problem here seems to be that you expect a single `char` to be the string class of C. There _is_ no string class in C. Everything is raw, manual arrays, `char[]`. So your question is actually "how to do string handling in C", which is too big a topic to answer here. The answer is to read the chapter about strings in your C book. – Lundin Mar 06 '19 at 08:35
  • 1
    As for the rest of your program, if the compiler doesn't shout warnings at you already, then enable more warnings and treat them as errors. For example, what do you *really* return from the `readin` function? – Some programmer dude Mar 06 '19 at 08:35
  • 1
    And your `search` function only seems to do some string comparison (in a very complicated way). You don't actually search through the *array* of `PhoneBk` elements to find the corresponding element. – Some programmer dude Mar 06 '19 at 08:38
  • 1
    Lastly, and to summarize things: It seems like you're guessing about plenty of things without much knowledge. Perhaps you should consider taking a few steps back, get a couple of beginners books to read, and start over from the beginning. – Some programmer dude Mar 06 '19 at 08:40
  • In `readin` the check for input `"#"` in the loop is wrong. When the loop condition is checked for the first time you will have initialized the first element's structure field as `"Test"`, but the next time it will check the next array element after the one you just entered. – Bodo Mar 06 '19 at 09:45
  • As pointed out by @Someprogrammerdude, it is clear that I don't know anything about how an array of structures is passed as a single pointer element. My point is that the pointer to the array of structures should be incremented every time, but I really don't know how to write the code for that (yes, I am that inexperienced). How, indeed, do you traverse an array of structures in a function when it's only been passed as a single pointer? – ProximanovaMetropolis Mar 06 '19 at 09:52
  • When an array is passed to a function, it decays to a pointer to its *first element*. The pointer you have in the function is a pointer to `s[0]`. You can access it through the usual array indexing like `p[0]` for the first element, `p[1]` for the second, etc. – Some programmer dude Mar 06 '19 at 09:56

2 Answers2

3

The program template is not well structured and gives a very bad example by using obsolete and dangerous function gets().

It would be much better to pass readin() the number of structures in the array pointed to by the p argument. From the code template, we must assume that MAX structures are accessible through p.

The readin function should read up to MAX entries from standard input and stores the into the corresponding elements of the destination array, using an index or incrementing p.

Here is a solution with an index:

int readin(PhoneBk *p) {
    int i, n = MAX;
    for (i = 0; i < n; i++) {
        printf("Enter person name, # to stop.\n");
        if (scanf("%19s", p[i].name) != 1 || p[i].name[0] == '#')
            return i;
        printf("Enter the telephone number.\n");
        if (scanf("%19s", p[i].telno) != 1)
            return i;
    }
    return i;
}

Simlarly, the search function can iterate through the array from 0 to size excluded:

void search(PhoneBk *p, int size, char *target) {
    int i, found = 0;
    for (i = 0; i < size; i++) {
        if (!strcmp(p[i].name, target) {
            printf("Name = %s, Tel. = %s\n", p[i].name, p[i].telno);
            found++;
        }
    }
    if (found == 0)
        printf("Name not found.\n");
}

Regarding the warnings, you have a typo on strcmp((char)p->name, "#"), the cast is unnecessary and in this case converts the pointer to a single char, causing 2 warnings: one for converting a pointer to an integer of a different size and another one for passing an integer to strcmp() instead of a pointer to char.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • I don’t know how you use scanf() in an if statement, so could you explain what that does? If scanf == 1, what happens? (Though I’m not familiar with %19s either, I can understand what that means: you can’t enter more than 19 characters.) – ProximanovaMetropolis Mar 06 '19 at 10:57
  • @Celcomtel _scanf_ returns the number of read element, in the current case there is 1 element to read (a string), this is why he compares to 1. _scanf("%19s", ..)_ allows to read a maximum of 19 chars, adding the final null char on the max case 20 chars are written, allowing to not go out of _name_ and _telno_. Do _man scanf_ or look on internet for more. You can look also at my answer, I use a different way to do. – bruno Mar 06 '19 at 11:26
2

In readin you do not test the last entered name but the name (not initialized) of the next entry, and you do not also check you reach the end of the phone book, nor the EOF case. Note you also suppose the name is a just a word and cannot contain spaces, but because the main uses the (ugly) gets may be composed names are allowed.

To not be perturbed by extra spaces it seems needed to remove them.

For instance:

#include <ctype.h>

void normalize(char * s)
{
  /* remove unexpected 'isspace' */
  int p1 = 0, p2 = 0;

  while (s[p2] != 0) {
    if (!isspace((unsigned char) s[p2]))
      s[p1++] = s[p2];
    else if ((p1 != 0) && (s[p1 - 1] != ' '))
      s[p1++] = ' ';
    p2 += 1;
  }

  s[((p1 != 0) && (s[p1 - 1] == ' ')) ? p1 - 1 : p1] = 0;
}

int readin(PhoneBk *p)
{
  int n;

  for (n = 0; n != MAX; ++n) {
    puts("Enter person name, max length 18, # to stop.");
    if (fgets(p->name, sizeof(p->name), stdin) == NULL) {
      /* EOF */
      return n;
    }
    normalize(p->name);
    if (p->name[0] == '#')
      break;

    printf("Enter the telephone number, max length 18\n");
    if (fgets(p->telno, sizeof(p->telno), stdin) == NULL) {
      /* EOF, forget partial entry */
      return n;
    }
    normalize(p->telno);

    p += 1;
  }

  return n;
}

Your search is so complicated I preferred to not try to understand it, sorry, the definition can be very simple :

void search(PhoneBk *p, int size, char *target)
{
  normalize(target);

  while (size--) {
    if (!strcmp(p->name, target)) {
      printf("%s found, phone number is %s\n", target, p->telno);
      return;
    }
    p += 1;
  }

  printf("unknown name %s\n", target);
}

Note I also normalize the read name, to be compatible with the read of the book.

If I try without changing the given main and its ugly gets, compilation and executions :

/tmp % gcc -g -pedantic -Wextra c.c
/tmp/ccdXFM1o.o: In function `main':
/tmp/c.c:17: warning: the `gets' function is dangerous and should not be used.
/tmp % ./a.out
Enter person name, max length 18, # to stop.
 john     do
Enter the telephone number, max length 18
12   34 56
Enter person name, max length 18, # to stop.
just me
Enter the telephone number, max length 18
1 2 3 4 5
Enter person name, max length 18, # to stop.
#
Enter search name: 
john  do
john do found, phone number is 12 34 56
/tmp % 
/tmp % ./a.out
Enter person name, max length 18, # to stop.
 john     do
Enter the telephone number, max length 18
12   34 56
Enter person name, max length 18, # to stop.
just me
Enter the telephone number, max length 18
1 2 3 4 5
Enter person name, max length 18, # to stop.
#
Enter search name: 
just        me
just me found, phone number is 1 2 3 4 5
/tmp % 
/tmp % ./a.out
Enter person name, max length 18, # to stop.
 john     do
Enter the telephone number, max length 18
12   34 56
Enter person name, max length 18, # to stop.
just me
Enter the telephone number, max length 18
1 2 3 4 5
Enter person name, max length 18, # to stop.
#
Enter search name: 
me  
unknown name me
bruno
  • 32,421
  • 7
  • 25
  • 37
  • 1
    Hello @bruno! There is a small problem with your approach: `fgets()` will read the newline into the destination array, so the maximum length of the input fields will be reduced by 1 byte. If the user enters 19 characters before the newline, the newline will not be read and the next call to `fgets()` will read the pending newline and return immediately. `scanf("%19s",...)` has the same problem, but for names longer than 19 characters. – chqrlie Mar 06 '19 at 16:04
  • 1
    Also another problem: the argument to `isspace()` should not have type `char` because on architectures where `char` is signed by default, negative characters will cause undefined behavior. You should write `isspace((unsigned char)s[p2])`. – chqrlie Mar 06 '19 at 16:10
  • Hello @chqrlie (as usual) you are right. About _isspace_ I supposed in my head it gets a _char_ but it get an _int_, so yes the sign counts, this is a *very* strange choice of the lib (no comparison with EOF). I edit my answer thank you again (and I hope you had a nice previous week, and I UV your answer some hours ago ^^) – bruno Mar 06 '19 at 16:20
  • 1
    Yes, it is a *strange* API: `isspace()` and friends can be passed any `int` value returned from `getc()`. `EOF` is OK and gives a 0 return value. The real problem is not this API, it is the fact that many compilers make `char` signed by default for compatibility with legacy code at the expense of consistency. I always instruct the compiler to make `char` unsigned by default (`-funsigned-char` for gcc and clang), but it is sadly not the default behavior. I am sure many programs can be broken by providing input with well chosen 8-bit characters. – chqrlie Mar 06 '19 at 16:31