2

I want to scan in a date in the form of mm/dd, so I've written this code:

#include <stdio.h>

int main (void)
{
  int start_date[4];

  scanf("%d%d/%d%d", &start_date[0], &start_date[1], &start_date[2], &start_date[3]);

  printf("%d%d%d%d\n", start_date[0], start_date[1], start_date[2], start_date[3]);

  return 0;
}

But when I enter the following for example:

04/20

scanf() reads this

4-419644000

How do I make it so that when I print out each element in start_date, I get this:

0420

when I enter the input from earlier?

  • I would use a loop to test each character to determine whether it's numeric, then combine the digits to string. Right now, scanf isn't parsing the / correctly. – Adrian M. Apr 21 '16 at 03:14

6 Answers6

7

Reading one digit at a time

Use %1d:

int n = scanf("%1d%1d/%1d%1d", &start_date[0], &start_date[1], &start_date[2], &start_date[3]);
if (n != 4) { …handle error… }

Note that this will accept both:

19/96

and

1 9/
9

6

as valid inputs. If you need the digits to be contiguous, you have to work harder. In general, it is usually best to read a line with fgets() (or POSIX's getline()) and then parse the line with sscanf(). You can also consider checking the length of the string, etc.

What went wrong?

Incidentally, you said:

But when I enter the following for example:

04/20

scanf() reads this

4-419644000

What happens here is that the 04 is read by the first %d; the second %d fails because / doesn't match a number, so nothing is written to &start_date[1] (or the other two items), and scanf() returns 1. Since you didn't check the return value, you weren't aware of the problem. Note that the check should be as I showed (n != 4 where 4 is the number of items you expect to be converted). Checking for EOF would not work correctly; there wasn't an EOF on the file, but there was a conversion failure. Since you printed an uninitialized variable, the value you got was indeterminate ('undefined behaviour' in the jargon); a largish negative number is reasonable (as is any other value whatsoever, or a crash, or …). In fact, you have three indeterminate numbers smushed together, of which only the first is negative. Avoid undefined behaviour; always check that your input operations succeed — and don't use the results if they fail. (Or, at least, be very cautious about using the results if they fail; you need to be sure you know what's going on.)

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
4

The %d would consume the string "04", if you want to input digit by digit really, try %c, then adjust them to digit:

char start_data[4];
scanf("%c%c/%c%c", &start_date[0], &start_date[1], &start_date[2], &start_date[3]);
start_data[0] -= '0';
start_data[1] -= '0';
start_data[2] -= '0';
start_data[3] -= '0';

Another way, you can just input mm and dd and do arithmetic on it:

int mm, dd;
scanf("%d/%d", &mm, &dd);
int m0 = mm % 10;
int m1 = mm / 10;
int d0 = dd % 10;
int d1 = dd / 10;
fluter
  • 13,238
  • 8
  • 62
  • 100
  • Note that using `%c` would store values in the range 48..57 (assuming a code set based on ASCII such as ISO 8859-15 or UTF-8), not values in the range 0..9. – Jonathan Leffler Apr 21 '16 at 03:16
2

Change the data type to char[], so your code look like this

 #include <stdio.h>

 int main (void)
 {
    char start_date[4];

    scanf("%c%c/%c%c", &start_date[0], &start_date[1], &start_date[2], &start_date[3]);

    printf("%c%c%c%c\n", start_date[0], start_date[1], start_date[2], start_date[3]);

    return 0;
 }

If you want to change the data type of each characters to integers you can change by look into the ASCII code then substract the characters with ASCII '0'

rizqi
  • 31
  • 5
  • Note that using `%c` would store values in the range 48..57 (assuming a code set based on ASCII such as ISO 8859-15 or UTF-8), not values in the range 0..9. – Jonathan Leffler Apr 21 '16 at 03:27
  • Yes, of course. So I leave a note that must change in accordance with the ASCII code. by reducing the value of each of the characters with ASCII '0' – rizqi Apr 21 '16 at 04:00
1

I think you should use strptime() instead. It's purpose-built for parsing standard date formats such as yours. Just read the entire "word" as a string and feed it to strptime().

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
1

This was too long for a comment, but you can see why this is happening here:

   0x080484a7 <+58>:    call   0x8048360 <__isoc99_scanf@plt>
   0x080484ac <+63>:    mov    0x2c(%esp),%ebx
   0x080484b0 <+67>:    mov    0x28(%esp),%ecx
   0x080484b4 <+71>:    mov    0x24(%esp),%edx
   0x080484b8 <+75>:    mov    0x20(%esp),%eax
   0x080484bc <+79>:    mov    %ebx,0x10(%esp)
   ...
End of assembler dump.
(gdb) break *main+79
Breakpoint 4 at 0x80484bc
(gdb) r
Starting program: /root/test/testcode
04/21
Breakpoint 4, 0x080484bc in main ()
(gdb) i r
eax            0x4      4
ecx            0x80484fb        134513915
edx            0xb7fff000       -1207963648
ebx            0xb7fcc000       -1208172544
esp            0xbffff720       0xbffff720
ebp            0xbffff758       0xbffff758
esi            0x0      0
edi            0x0      0
eip            0x80484bc        0x80484bc <main+79>
eflags         0x286    [ PF SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb) c
Continuing.

4-1207963648134513915-1208172544

Here's whats happening:

mov    0x2c(%esp),%ebx
mov    0x28(%esp),%ecx
mov    0x24(%esp),%edx
mov    0x20(%esp),%eax

These four mov statements are preparation for the printf() call. Each %d corresponds to one of the registers: eax ebx ecx edx
You can see that it is moving four bytes into each register. This is the reason you see gibberish. Each %d getting printed is expecting a 4-byte integer.

I set a breakpoint after all four of those mov statements, so lets look at what they contain:

eax            0x4              4
ecx            0x80484fb        134513915
edx            0xb7fff000       -1207963648
ebx            0xb7fcc000       -1208172544

This doesn't look right at all, but it does indeed correspond to what's on the stack:

0xbffff740:     0x00000004      0xb7fff000      0x080484fb      0xb7fcc000

These are the four values that wind up in the registers.
The first one is actually your 04 combined. It interpreted 04 as being within the bounds of %d, which is a 4-byte integer on my machine.
Since you tried to grab four %d's, the program continues as it should and prints out four %ds. In other words, it prints out four 4-byte integers.

The reason this is happening is what Jonathan Leffler explained: scanf is gobbling up the 04 into the first %d, then breaking on the /
You still have a printf() of %d%d%d%d though, so the program dutifully prints out the remaining 4-byte decimal values. Since scanf failed, the remaining 3 %ds contain whatever happened to be on the stack.

Community
  • 1
  • 1
MikeTGW
  • 395
  • 2
  • 10
  • Well done for trying to answer what went wrong. I'm not sure your explanation is all that comprehensible, or that delving into assembler is necessary. As I note in an update to my answer, the problem is simply that the first `%d` reads the `04`, the second `%d` fails because `/` is not a number, and therefore none of the other three variables is set by `scanf()` and hence 3 uninitialized values are printed, the first of which happens to be negative. The code ignores the status from `scanf()` so it is unaware that `scanf()` reported its troubles. – Jonathan Leffler Apr 21 '16 at 05:06
  • I am not familiar with how scanf() works and I didnt want to step through it. Failing on the `/` makes sense why the rest of the `mov`'s are failing though. I hoped my edit would have cleared up what is being printed, Ill attempt to make it even more readable – MikeTGW Apr 21 '16 at 05:31
0

Unless you have another reason for using an array to store the individual digits, I suggest you just store the month and day as variables.

Also, because you know that there is a separator, accomodate it in your code. It is simpler.

This is the code you are looking for.

#include <stdio.h>

int main (void)
{
  int month, day;
  char sep;

  scanf("%d%c%d", &month, &sep, &day);

  printf("%d of %d\n", day, month);
  
  return 0;
}

Explanation for the printing (Printing leading 0's in C):

The 0 indicates what you are padding with and the 5 shows the width of the integer number.

Example 1: If you use "%02d" (useful for dates) this would only pad zeros for numbers in the ones column. E.g., 06 instead of 6.

Example 2: "%03d" would pad 2 zeros for one number in the ones column and pad 1 zero for a number in the tens column. E.g., number 7 padded to 007 and number 17 padded to 017.

You could have also taken the data in as a string using %5s and further extract that data from the array of characters (string). Here is a good resource with examples. -> https://cplusplus.com/reference/cstdio/scanf/

I do feel that for your solution it would still have been an overkill, so I beleive the example code provided above is sufficient.