As well known, former gets() offers no control/detection of buffer overflow leading to UB. It could have had it had a size parameter.
In addition to @William Pursel good answer concerning int range.
scanf("%d", ...): Input not limited to one line.
gets() read 1 line. "%d" in scanf(), first consumes leading white-space which may include several lines.
scanf("%d", ...): does not read the whole line.
Unlike gets(), scanf("%d", ...) leaves any input after the input for the int. This often includes a '\n'. Not reading the entire lines often sets the seed for subsequent problems.
Depending on goals, scanf("%d", ...) does not complain about trailing non-numeric text.
C lacks a robust ways to read a line. IMO, fgets(), gets_s(), scanf(anything), extension getline() all lack some functionality.
I'd campaign for a int scan_line(size_t sz, char *buf /*, size_t *length_read*/) that always reads a line, always forms a string in buf and returns EOF (end-of-file, input error), 1 on success and 0 when sz is too small.
Alternatively (and more debatable) *scanf() could be improved:
Add ability to pass in size for "%s" and friends. This is sorely needed.
Defined behavior on int overflow.
Something like "%#\n" to scan in white-space, but not '\n'. Does not contribute to the return value.
Something like "%\n" to scan in 1 '\n'. Contributes to the return value. May use a leading space "% \n" to allow optional leading non-'\n' white-space.
Offer *scanfln() which always read just 1 line.