2
\$\begingroup\$

Task

Write a Program that requests the user's first name and then the user's last name. Have it print the entered names on one line and the number of letters in each name on the following line. Align each letter count with the end of the corresponding name, as in the following:

Klaus Dieter
    5      6

My Try

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

void printWhitespace(int times) {
    for (int i = 0; i < times; i++) {
        printf(" ");
    }
}

int main(void) {
    // get names
    printf("first name: ");
    char firstName[20];
    scanf("%s", &firstName);
    printf("last name: ");
    char lastName[20];
    scanf("%s", &lastName);

    // display names
    printf("%s %s\n", firstName, lastName);

    // display number of characters under last character of names
    int numOfWhitespace = strlen(firstName) - 1;
    printWhitespace(numOfWhitespace);
    printf("%d ", strlen(firstName));

    numOfWhitespace = strlen(lastName) - 1;
    printWhitespace(numOfWhitespace);
    printf("%d\n", strlen(lastName));
}

Does the author expect that from me? Or is there a better way to write that?

\$\endgroup\$

closed as primarily opinion-based by πάντα ῥεῖ, t3chb0t, alecxe, Graipher, Mast Jan 1 at 13:46

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 3
    \$\begingroup\$ "Does the author expect that from me?" How would we know? Ask him. \$\endgroup\$ – Mast Dec 31 '18 at 17:20
  • \$\begingroup\$ Does C now allow for inline variable declarations anywhere in code like C++ does? Or does it still require the variable declarations to come at the top of a scope before any statements? \$\endgroup\$ – selbie Dec 31 '18 at 20:08
  • 1
    \$\begingroup\$ @selbie C99 allowed object declarations in many places. As well as C11, C18. \$\endgroup\$ – chux Dec 31 '18 at 20:48
5
\$\begingroup\$

Small sugggestion for printWhitespace(). You could do the following:

void printWhitespace(const unsigned int times) {
    printf("%*s", times, " ");
}

I have made the function argument const as it probably isn't meant to be modified. it is generally a good idea to be as "const" as possible as this avoids the mistake of writing to a variable that should be read-only.


I would put the array declarations at the top of the function before any of the code with a blank line between the declarations and the first line of code.


The function scanf can be used with caution... it can lead to buffer overflow attacks in the way it is used in your code. If the user enters a string longer than 19 characters (last character would be filled in as a null terminator in the buffer), scanf will just write on past the end of the buffer.

You could help guard against this by using scanf("%19s", firstName); and scanf("%19s", lastName);. As @chux points out the length is one less than the buffer size. This is because "String input conversions store a terminating null byte ('\0') to mark end of the input; the maximum field width does not include this terminator." -- quote from man page.

Looked into this a little more and this SO answer, the author says:

Note that the POSIX 2008 (2013) version of the scanf() family of functions supports a format modifier m (an assignment-allocation character) for string inputs (%s, %c, %[). Instead of taking a char * argument, it takes a char ** argument, and it allocates the necessary space for the value it reads

That would be a useful way of avoiding buffer overflow, but you must remember to free() the buffer returned.


The variable numOfWhitespace can also be const. Might put that to top of function too.


Your last bit of code that tries to align the numbers to the end of the words will only align properly if the string length is 9 or less. If the string length is greater then the number will be double digits so you could account for this.


Add return 0; to the end of the function. You reach the end of a non-void function without returning anything...


Hope that helps :)

\$\endgroup\$
  • 1
    \$\begingroup\$ I disagree with your const suggestion. Have a read through this answer - softwareengineering.stackexchange.com/a/204720 - with which I agree on most points. const is not useful on arguments unless the arguments are referential. \$\endgroup\$ – Reinderien Dec 31 '18 at 14:09
  • 3
    \$\begingroup\$ As of C99, return 0; in main is optional, compare stackoverflow.com/q/4138649/1187415. \$\endgroup\$ – Martin R Dec 31 '18 at 14:26
  • 2
    \$\begingroup\$ @Reinderien: Fair enough. I can see your point of view. Personally, I think it can still be useful, because if you don't indend the parameter to be written you can still get the compiler to check for a write-in-error to that var. \$\endgroup\$ – Jimbo Dec 31 '18 at 15:50
  • 2
    \$\begingroup\$ @MartinR Even though return 0 is technically optional, leaving it out is still a bad idea IMO. It's a weird language non-uniformity and I don't like non-uniformity. \$\endgroup\$ – Reinderien Dec 31 '18 at 15:54
  • \$\begingroup\$ scanf("%20s", &firstName); is off by 1. scanf("%19s", firstName); is better to limit the width of input to 19 characters to store as a string in char firstName[20] \$\endgroup\$ – chux Dec 31 '18 at 20:53
4
\$\begingroup\$

running the posted code through the compiler results in:

gcc -ggdb -Wall -Wextra -Wconversion -pedantic -std=gnu11 -c "untitled.c"

untitled.c: In function ‘main’:
untitled.c:14:13: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[20]’ [-Wformat=]
     scanf("%s", &firstName);
            ~^   ~~~~~~~~~~

untitled.c:17:13: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[20]’ [-Wformat=]
     scanf("%s", &lastName);
            ~^   ~~~~~~~~~

untitled.c:23:27: warning: conversion to ‘int’ from ‘size_t {aka long unsigned int}’ may alter its value [-Wconversion]
     int numOfWhitespace = strlen(firstName) - 1;
                           ^~~~~~

untitled.c:25:14: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘size_t {aka long unsigned int}’ [-Wformat=]
     printf("%d ", strlen(firstName));
             ~^    ~~~~~~~~~~~~~~~~~
             %ld

untitled.c:27:23: warning: conversion to ‘int’ from ‘size_t {aka long unsigned int}’ may alter its value [-Wconversion]
     numOfWhitespace = strlen(lastName) - 1;
                       ^~~~~~

untitled.c:29:14: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘size_t {aka long unsigned int}’ [-Wformat=]
     printf("%d\n", strlen(lastName));
             ~^     ~~~~~~~~~~~~~~~~
             %ld

strongly suggest correcting all the above problems

regarding:

scanf("%s", &firstName);

When calling any of the scanf() family of functions, 1) always check the returned value (not the parameter values) to assure the operation was successful. 2) when using the input format specifier: '%s' and/or '%[...]', always include a MAX CHARACTERS modifier that is 1 less than the length of the input buffer because those specifiers always append a NUL char to the input. This avoids any possibility of a buffer overrun and the resulting undefined behavior.

regarding:

int numOfWhitespace = strlen(firstName) - 1;`

the function: strlen() returns a size_t, so the variable: numOfWhitespace should be declared as size_t, not int

regarding:

printf(" ");

the function: printf() is very expensive in CPU cycles. better to use:

putc( ' ', stdout );

regarding this kind of statement;

printf("%d ", strlen(firstName));

the function strlen() returns a size_t, so the output format specifier should be %lu

Note: in C, referencing an array name degrades to the address of the first byte of the array, so given the above comments, this:

scanf("%s", &firstName);

should be:

if( scanf("%19s", firstName) != 1 )
{
    fprintf( stderr, "failed to input first name\n" );
    exit( EXIT_FAILURE );
}

where exit() and EXIT_FAILURE are from the header file: stdlib.h

the above should be enough to enable you to correct the problems.

for ease of readability and understanding: insert an appropriate space: inside parens, inside braces, inside brackets, after commas, after semicolons, around C operators

\$\endgroup\$
  • \$\begingroup\$ Wow, you seem to be a real pro, thank you for that comment! :) \$\endgroup\$ – Vengeancos Dec 31 '18 at 16:27
  • \$\begingroup\$ printf(" "); vs. putc( ' ', stdout ); expense. Good compilers will analyze this and emit the same efficient code. \$\endgroup\$ – chux Dec 31 '18 at 20:37
  • 1
    \$\begingroup\$ "function strlen() returns a size_t, so the output format specifier should be %lu" misleads. The matching print specifier for size_t is "%zu", "%zx" ..., not "%lu". If the platform is pre C99, best to use printf("%lu ", (unsigned long) strlen(firstName)); \$\endgroup\$ – chux Dec 31 '18 at 20:40
2
\$\begingroup\$

Program that requests the user's first name and then the user's last name

Spaces can exist in names

First names: "Betty Jo", "John Paul"

Last names: "Van Gogh" , "Smith Davis"

scanf("%s", &lastName); fails if the name contains a space. Both first and last names, independently may contain embedded spaces.

Alternative:

size_t trim(char *s) {
  char *start = s;
  while (isspace((unsigned char) *start)) {
    start++;  
  } 
  size_t len = strlen(start);
  while (len > 0 && isspace((unsigned char) start[len-1]) {
    len--;
  }
  start[len] = '\0';
  memmove(s, start, len + 1);
  return len;
} 

// return 1 on success
// return EOF on end-of-file/error (and no name read)
// return 0 otherwise (name too short (0), name too long)
size_t getname(const char *prompt, char *name, size_t sz) {
  fputs(prompt, stdout);
  fflush(stdout);
  char buffer[sz*2 + 2];  // allow for lots of extra leading, trailing spaces
  if (fgets(buffer, sizeof buffer, stdin) == NULL) {
    return EOF;
  }
  size_t len = trim(buffer);
  if (len == 0 || len >= sz) {
    return 0;
  }
  memcpy(name, buffer, len + 1); // or strcpy(name, len)
  return 1;
}

Names may well exceed 19 characters.

600+ example

Hawaiian Woman Gets IDs That Fit Her 36-Character Last Name

Avoid hard coding such a small value. Best to set as a defined constant. The key point is production code get this value from a program specification. Be prepared to adjust your code nimbly to handle that.

#define NAME_FIRST_N 100
#define NAME_LAST_N 700

char firstName[NAME_FIRST_N];
if (getname("first name: ", firstName, sizeof firstName) != 1) {
  ; //Handle problem.
}

char lastName[NAME_LAST_N];
if (getname("last name: ", lastName, sizeof lastName) != 1) {
  ; //Handle problem.
}

Alignment

Simply prepend "*" to specify the width of the integer.

//numOfWhitespace = strlen(lastName) - 1;
//printWhitespace(numOfWhitespace);
//printf("%d\n", strlen(lastName));

int len = (int) strlen(lastName));
printf("%*d\n", len, len);
//              ^^^------------- Minimum print width, pad with spaces.
\$\endgroup\$
  • \$\begingroup\$ Why is 1 returned on success? Isn't 0 the only recommended success code as defined by the C standard (with non-zero used for all errors)? \$\endgroup\$ – Faraz Jan 1 at 15:24
  • 1
    \$\begingroup\$ @Faraz it easy enough for one to amend code to your liking. The model getname() is like other input standard library functions such as scanf(format, &object) which would return 1 on success, EOF on end-of-file/error and 0 on early matching failure. This is useful in that the caller typically needs at least a 3-way distinction. Not a 2-way as suggested by 0:good, non-0:error. EOF is not a error so much as a signal that input is done. .... \$\endgroup\$ – chux Jan 1 at 16:04
  • 1
    \$\begingroup\$ @Faraz ... C does not uniformly indicates error in only 1 manner. strtol() with its errno, endptr and malloc() with its NULL sometimes indicates an error are examples of other schemes. \$\endgroup\$ – chux Jan 1 at 16:05
  • \$\begingroup\$ I appreciate the clarification @chux, this helps better understand the reasoning for that. Thank you! \$\endgroup\$ – Faraz Jan 1 at 16:09

Not the answer you're looking for? Browse other questions tagged or ask your own question.