为什么我在使用这段代码时会抛出异常,有人能解释一下吗?

huangapple go评论58阅读模式
英文:

Can someone explain to me why i am getting an exception thrown with this code

问题

我的代码应该扫描用户名和密码,并确保它们不超过最大限制。如果超过了最大限制,应重新提示用户输入。

#define _CRT_SECURE_NO_WARNINGS

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

#define MAXNAME 30

int main() {
    char username_input[MAXNAME];
    char password_input[MAXNAME];

    username_input[MAXNAME - 1] = '
#define _CRT_SECURE_NO_WARNINGS

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

#define MAXNAME 30

int main() {
    char username_input[MAXNAME];
    char password_input[MAXNAME];

    username_input[MAXNAME - 1] = '\0';
    password_input[MAXNAME - 1] = '\0';

    printf("请输入您的用户名: ");
    while (scanf("%s", username_input) != 1 || strlen(username_input) > MAXNAME) {
        printf("输入不正确。\n请再次输入您的用户名: ");
    }

    printf("请输入您的密码: ");
    while (scanf("%s", password_input) != 1 || strlen(password_input) > MAXNAME) {
        printf("输入不正确。\n请再次输入您的密码: ");
    }
    return 0;
}
'
;
password_input[MAXNAME - 1] = '
#define _CRT_SECURE_NO_WARNINGS

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

#define MAXNAME 30

int main() {
    char username_input[MAXNAME];
    char password_input[MAXNAME];

    username_input[MAXNAME - 1] = '\0';
    password_input[MAXNAME - 1] = '\0';

    printf("请输入您的用户名: ");
    while (scanf("%s", username_input) != 1 || strlen(username_input) > MAXNAME) {
        printf("输入不正确。\n请再次输入您的用户名: ");
    }

    printf("请输入您的密码: ");
    while (scanf("%s", password_input) != 1 || strlen(password_input) > MAXNAME) {
        printf("输入不正确。\n请再次输入您的密码: ");
    }
    return 0;
}
'
;
printf("请输入您的用户名: "); while (scanf("%s", username_input) != 1 || strlen(username_input) > MAXNAME) { printf("输入不正确。\n请再次输入您的用户名: "); } printf("请输入您的密码: "); while (scanf("%s", password_input) != 1 || strlen(password_input) > MAXNAME) { printf("输入不正确。\n请再次输入您的密码: "); } return 0; }

我遇到的问题是,每当进入其中一个 while 循环时,我会得到重新提示,并且能够继续执行代码,但一旦我达到 return 0,就会引发异常。

异常信息为:Run-Time Check Failure #2 - Stack around the variable 'username_input' was corrupted。

我尝试过使用 scanf_s 和 fgets(),但似乎都没有起作用。

英文:

My code is supposed to scan a username and password and make sure that they aren't greater than the max amount. If they are, it should re-prompt the user to enter something.

#define _CRT_SECURE_NO_WARNINGS

#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

#define MAXNAME 30

int main() {
    char username_input[MAXNAME];
    char password_input[MAXNAME];

    username_input[MAXNAME - 1] = &#39;
#define _CRT_SECURE_NO_WARNINGS
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#define MAXNAME 30
int main() {
char username_input[MAXNAME];
char password_input[MAXNAME];
username_input[MAXNAME - 1] = &#39;\0&#39;;
password_input[MAXNAME - 1] = &#39;\0&#39;;
printf(&quot;Please enter your username: &quot;);
while (scanf(&quot;%s&quot;, username_input) != 1 || strlen(username_input) &gt; MAXNAME) {
printf(&quot;Improper input.\nPlease enter your username: &quot;);
}
printf(&quot;Please enter your password: &quot;);
while (scanf(&quot;%s&quot;, password_input) != 1 || strlen(password_input) &gt; MAXNAME) {
printf(&quot;Improper input.\nPlease enter your password: &quot;);
}
return 0;
}
&#39;; password_input[MAXNAME - 1] = &#39;
#define _CRT_SECURE_NO_WARNINGS
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#define MAXNAME 30
int main() {
char username_input[MAXNAME];
char password_input[MAXNAME];
username_input[MAXNAME - 1] = &#39;\0&#39;;
password_input[MAXNAME - 1] = &#39;\0&#39;;
printf(&quot;Please enter your username: &quot;);
while (scanf(&quot;%s&quot;, username_input) != 1 || strlen(username_input) &gt; MAXNAME) {
printf(&quot;Improper input.\nPlease enter your username: &quot;);
}
printf(&quot;Please enter your password: &quot;);
while (scanf(&quot;%s&quot;, password_input) != 1 || strlen(password_input) &gt; MAXNAME) {
printf(&quot;Improper input.\nPlease enter your password: &quot;);
}
return 0;
}
&#39;; printf(&quot;Please enter your username: &quot;); while (scanf(&quot;%s&quot;, username_input) != 1 || strlen(username_input) &gt; MAXNAME) { printf(&quot;Improper input.\nPlease enter your username: &quot;); } printf(&quot;Please enter your password: &quot;); while (scanf(&quot;%s&quot;, password_input) != 1 || strlen(password_input) &gt; MAXNAME) { printf(&quot;Improper input.\nPlease enter your password: &quot;); } return 0; }

The problem that I am facing is that whenever one of these while loops are entered, I will get the re-prompt fine and I will be able to continue with the code, but once i hit return 0 I will have an exception thrown.

The exception reads: Run-Time Check Failure #2 - Stack around the variable 'username_input' was corrupted.

I have tried using scanf_s and fgets() as well, those haven't seemed to work.

答案1

得分: 0

以下是您要翻译的内容:

Try this:
```c
#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;
#include &lt;string.h&gt;

#define MAX_LEN 30

void read_ident(char const * descr, char buf[MAX_LEN + 2])
{
    do {
        printf(&quot;Please enter your %s: &quot;, descr);
        if (scanf(&quot;%31[^\n]&quot;, buf) == 1 &amp;&amp; strlen(buf) &lt;= MAX_LEN) {
            break;
        }
        printf(&quot;Improper input.\n&quot;);
        scanf(&quot;%*[^\n]&quot;);
        scanf(&quot;%*c&quot;);
    } while (!feof(stdin));
    scanf(&quot;%*c&quot;);
}

int main()
{
    char username_input[MAX_LEN + 2], password_input[MAX_LEN + 2];
    read_ident(&quot;username&quot;, username_input);
    read_ident(&quot;password&quot;, password_input);
}

I love scanf, but you might find that fgets is more convenient. See reference.

The idea of my code above is to read 31 characters at most, before the end of the line. If you don't get more than 30 characters, you're done. Otherwise, %*[^\n] skips all characters before the end of the line (if any: if the end of the line immediately follows, this attempt to read will fail, this is why we must have two consecutive scanf), then %*c skips the end of the line character ("line feed").

If you don't want to or cannot hard code the length of your buffer, you could write something like that:

char format[16];
sprintf(&quot;%%%d[^\n]&quot;, MAX_LEN + 1);
scanf(format, buf);

As said by @Retired Ninja, your code crashes because you don't tell scanf how many characters it should read at most, so it may overflow your buffer. Reading strings with scanf shouldn't normally be done without specifying a width (unless you don't care about safety).

Here is how to do with fgets:

void read_ident(char const * descr, char buf[MAX_LEN + 2])
{
    do {
        printf(&quot;Please enter your %s: &quot;, descr);
        fgets(buf, MAX_LEN + 2, stdin);
        size_t nb_read_chars = strlen(buf);
        if (nb_read_chars &gt;= 2 &amp;&amp; buf[nb_read_chars - 1] == &#39;\n&#39;) {
            buf[nb_read_chars - 1] = &#39;\0&#39;
            break;
        }
        printf(&quot;Improper input.\n&quot;);
        if (nb_read_chars &gt;= 2) {
            while (fgetc(stdin) != &#39;\n&#39;);
        }
    } while (!feof(stdin));
}

I do prefer scanf.

scanf_s won't help because they trigger an exception if there is an error, this is not what you want to do (and I think you will rarely want to do that).


<details>
<summary>英文:</summary>

Try this:
```c
#include &lt;stdio.h&gt;
#include &lt;stdbool.h&gt;
#include &lt;string.h&gt;

#define MAX_LEN 30

void read_ident(char const * descr, char buf[MAX_LEN + 2])
{
    do {
        printf(&quot;Please enter your %s: &quot;, descr);
        if (scanf(&quot;%31[^\n]&quot;, buf) == 1 &amp;&amp; strlen(buf) &lt;= MAX_LEN) {
            break;
        }
        printf(&quot;Improper input.\n&quot;);
        scanf(&quot;%*[^\n]&quot;);
        scanf(&quot;%*c&quot;);
    } while (!feof(stdin));
    scanf(&quot;%*c&quot;);
}

int main()
{
    char username_input[MAX_LEN + 2], password_input[MAX_LEN + 2];
    read_ident(&quot;username&quot;, username_input);
    read_ident(&quot;password&quot;, password_input);
}

I love scanf, but you might find that fgets is more convenient. See reference.

The idea of my code above is to read 31 characters at most, before the end of line. If you don’t get more than 30 characters you’re done. Otherwise, %*[^\n] skips all characters before the end of line (if any: if the end of line immediately follows, this attempt to read will fail, this is why we must have two consecutive scanf), then %*c skips the end of line character (“line feed”).

If you don’t want to or cannot hard code the length of your buffer, you could write something like that:

char format[16];
sprintf(&quot;%%%d[^\n]&quot;, MAX_LEN + 1);
scanf(format, buf);

As said by @Retired Ninja, your code crashes because you don’t tell scanf how many characters it should read at most, so it may overflow your buffer. Reading strings with scanf shouldn’t normally be done without specifying a width (unless you don’t care about safety).

Here is how to do with fgets:

void read_ident(char const * descr, char buf[MAX_LEN + 2])
{
    do {
        printf(&quot;Please enter your %s: &quot;, descr);
        fgets(buf, MAX_LEN + 2, stdin);
        size_t nb_read_chars = strlen(buf);
        if (nb_read_chars &gt;= 2 &amp;&amp; buf[nb_read_chars - 1] == &#39;\n&#39;) {
            buf[nb_read_chars - 1] = &#39;\0&#39;;
            break;
        }
        printf(&quot;Improper input.\n&quot;);
        if (nb_read_chars &gt;= 2) {
            while (fgetc(stdin) != &#39;\n&#39;);
        }
    } while (!feof(stdin));
}

I do prefer scanf.

scanf_s won’t help because they trigger an exception if there is an error, this is not what you want to do (and I think you will rarely want to do that).

答案2

得分: 0

作为一个C程序员,一切都很愉快,直到有一天我们需要处理来自用户的可变长度输入。
所有用于“读取用户输入”的libc API都只接受“固定缓冲区”,就像scanffgets等等,
但我们不知道用户在实际输入之前会输入多长,因此我们不知道应该为这些scanf/fgets制作多大的缓冲区。

通常有两种选择。
一种是限制用户输入,并丢弃超出限制的任何数据,
另一种是“动态分配”——
我们首先分配一个缓冲区,然后以最小单位“追加”用户输入到缓冲区中,
如果我们的缓冲区空间不足,我们在进一步放入之前将其扩大。

这种方法有点啰嗦,我们应该使其“可重用”。
以下是一个示例:

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

#define DB_BLOCK_SIZE (100)

typedef struct tagDynaBuf {
    char * buf;
    unsigned int capacity;
    unsigned int size;
} DynaBuf;

void dynabuf_init(DynaBuf * db){
    memset((void*)db, 0, sizeof(*db));
}

void dynabuf_release(DynaBuf * db){
    if (!db){
        return;
    }

    if (!db->buf){
        return;
    }

    free(db->buf);
    memset((void*)db, 0, sizeof(*db));
}

int dynabuf_append_char(DynaBuf * db, int c){

    if (!db){
        fprintf(stderr,"%u: 'db' was null.\n", __LINE__);
        return -1;
    }

    // 1. 让我们确保我们的缓冲区有足够的空间。
    if (!db->buf){
        // 第一次分配内存
        db->buf = malloc(DB_BLOCK_SIZE);
        if (!db->buf ){
            fprintf(stderr,"%u: 第一次分配内存失败。\n", __LINE__);
            return -2;
        }

        db->capacity = DB_BLOCK_SIZE;
        db->size = 0;
    }

    if (db->capacity == db->size ){
        // 哦哦,我们的缓冲区空间用完了,让我们把它变大。
        char * new_buf = realloc( db->buf, db->capacity + DB_BLOCK_SIZE);
        if (!new_buf)
        {
            fprintf(stderr,"%u: 扩大缓冲区失败,为 %u。\n", __LINE__, db->capacity + DB_BLOCK_SIZE);
            return -3;
        }

        db->capacity += DB_BLOCK_SIZE;
        db->buf = new_buf;
    }

    // 现在我们可以自信地接受新来者——'c'。

    db->buf[db->size] = c;
    db->size ++;

    return 0;
}

int main(int argc, char ** argv){
    int ret = 0;
    DynaBuf buf;
    dynabuf_init(&buf);

    printf("尽量输入文本(以ctrl-D结束),我将尽力处理它们:\n");

    while (1){
        int c = fgetc(stdin);
        if ( EOF == c)
        {
            break;
        }

        if ( dynabuf_append_char(&buf, c)){
            ret = 1;
            goto EXIT;
        }
    }

    // 使我们的缓冲区以'\0'结束。
    if ( dynabuf_append_char(&buf, 0)){
        ret = 2;
        goto EXIT;
    }

    printf("\n\n谢谢!我得到了:\n%s\n", buf.buf);

EXIT:
    dynabuf_release(&buf);
    return ret;
}

你甚至可以将其测试为 cat {大文本文件} | ./aout
希望这能有所帮助。

英文:

Being a C programmer is happy until one day we need to handle variant length input from user.
All libc APIs for 'reading user input' take only 'fix buffer' -- as you can see like scanf, fgets ... ,
but we don't know how long user will input before he/she actually inputs,
so we have no idea 'how large buffer we should make for those scanf/fgets'.

Usually there are 2 options.
One is to limited user input, and discard any data exceed the limitation,
and another is 'dynamic allocation' --
we first allocate a chunk of buffer then 'append' user input in minimal unit to the buffer,
if we would run out buffer space, we enlarge it before we put futher more into it.

The approach is somehow verbose, we should make it 'reusable'.
Here is a sample:

#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;malloc.h&gt;

#define DB_BLOCK_SIZE (100)

typedef struct tagDynaBuf {
    char * buf;
    unsigned int capacity;
    unsigned int size;
} DynaBuf;

void dynabuf_init(DynaBuf * db){
    memset((void*)db, 0, sizeof(*db));
}

void dynabuf_release(DynaBuf * db){
    if (!db){
        return;
    }

    if (!db-&gt;buf){
        return;
    }

    free(db-&gt;buf);
    memset((void*)db, 0, sizeof(*db));
}

int dynabuf_append_char(DynaBuf * db, int c){

    if (!db){
        fprintf(stderr,&quot;%u: &#39;db&#39; was null.\n&quot;, __LINE__);
        return -1;
    }

    // 1. Let&#39;s make sure we have enough space in out buf.
    if (!db-&gt;buf){
        // malloc for the 1st time
        db-&gt;buf = malloc(DB_BLOCK_SIZE);
        if (!db-&gt;buf ){
            fprintf(stderr,&quot;%u: failed in 1st malloc.\n&quot;, __LINE__);
            return -2;
        }

        db-&gt;capacity = DB_BLOCK_SIZE;
        db-&gt;size = 0;
    }

    if (db-&gt;capacity == db-&gt;size ){
        // Ohoh, we ran out of our buf space, let&#39;s make it larger.
        char * new_buf = realloc( db-&gt;buf, db-&gt;capacity + DB_BLOCK_SIZE);
        if (!new_buf)
        {
            fprintf(stderr,&quot;%u: failed in enlarging buf for %u.\n&quot;, __LINE__, db-&gt;capacity + DB_BLOCK_SIZE);
            return -3;
        }

        db-&gt;capacity += DB_BLOCK_SIZE;
        db-&gt;buf = new_buf;
    }

    // Now we can accecpt the new comer -- &#39;c&#39; -- with confidence.

    db-&gt;buf[db-&gt;size] = c;
    db-&gt;size ++;

    return 0;
}

int main(int argc, char ** argv){
    int ret = 0;
    DynaBuf buf;
    dynabuf_init(&amp;buf);

    printf(&quot;Enter text as long as you can (end with ctrl-D), I will try my best to handle them:\n&quot;);

    while (1){
        int c = fgetc(stdin);
        if ( EOF == c)
        {
            break;
        }

        if ( dynabuf_append_char(&amp;buf, c)){
            ret = 1;
            goto EXIT;
        }
    }

    // make our buf \0 terminated.
    if ( dynabuf_append_char(&amp;buf, 0)){
        ret = 2;
        goto EXIT;
    }

    printf(&quot;\n\nThank you! I got:\n%s\n&quot;, buf.buf);

EXIT:
    dynabuf_release(&amp;buf);
    return ret;
}

You can even test it as cat {a large text file} | ./aout.
Hope it could help.

huangapple
  • 本文由 发表于 2023年4月4日 09:43:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75924890.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定