询问 (char*) 的 malloc

0 c dynamic-memory-allocation

我只是想知道为什么

students[x].firstName=(char*)malloc(sizeof(char*));
Run Code Online (Sandbox Code Playgroud)

这个不需要字符串的长度。

完整代码(来自互联网上的某个地方):

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
    typedef struct
    {
        char* firstName;
        char* lastName;
        int rollNumber;

    } STUDENT;

    int numStudents=2;
    int x;
    STUDENT* students = (STUDENT *)malloc(numStudents * sizeof *students);
   
    for (x = 0; x < numStudents; x++)
    {
        students[x].firstName=(char*)malloc(sizeof(char*));
       
        printf("Enter first name :");
        scanf("%s",students[x].firstName);
        students[x].lastName=(char*)malloc(sizeof(char*));
        printf("Enter last name :");
        scanf("%s",students[x].lastName);
        printf("Enter roll number  :");
        scanf("%d",&students[x].rollNumber);
    }

    for (x = 0; x < numStudents; x++)
        printf("First Name: %s, Last Name: %s, Roll number: %d\n", students[x].firstName, students[x].lastName, students[x].rollNumber);

    return (0);
}
Run Code Online (Sandbox Code Playgroud)

解释为:

students[x].firstName=(char*)malloc(sizeof(char*));
Run Code Online (Sandbox Code Playgroud)

And*_*zel 7

根据您使用的平台,该行

students[x].firstName=(char*)malloc(sizeof(char*));
Run Code Online (Sandbox Code Playgroud)

最有可能相当于

students[x].firstName = malloc( 4 );
Run Code Online (Sandbox Code Playgroud)

或者

students[x].firstName = malloc( 8 );
Run Code Online (Sandbox Code Playgroud)

char *因为这是大多数平台上 a (指向 的指针)的大小char

这意味着如果用户输入超过 3 或 7 个字符(4 或 8 个字符,包括终止空字符),则这些行

printf("Enter first name :");
scanf("%s",students[x].firstName);
Run Code Online (Sandbox Code Playgroud)

将导致缓冲区溢出,从而调用未定义的行为。这意味着您的程序可能会崩溃或以其他方式出现异常。

因此,您的担忧是有道理的。线路

students[x].firstName=(char*)malloc(sizeof(char*));
Run Code Online (Sandbox Code Playgroud)

应该指定一个足以存储用户输入的最大可能长度的大小,而不是仅指定指针的大小(4 或 8 字节)。

但是,即使您指定一个更高的值,例如

students[x].firstName = malloc( 200 );
Run Code Online (Sandbox Code Playgroud)

那么你的程序仍然不安全,因为用户输入长度至少为 200 个字符的单词仍然可能导致缓冲区溢出。因此,这样写会更安全

scanf( "%199s", students[x].firstName );
Run Code Online (Sandbox Code Playgroud)

代替:

scanf("%s",students[x].firstName);
Run Code Online (Sandbox Code Playgroud)

这会将写入内存缓冲区的字符数限制为scanf200 个(199 个匹配字符加上终止空字符)。

然而,即使这个解决方案更好,它仍然不理想,因为scanf如果输入太长,它会默默地截断输入,并将剩余的行留在输入流上。这意味着,当您下次调用scanf输入流时,剩余的输入可能会导致麻烦,因为这是首先读取的内容。

因此,最好始终读取整行输入,如果太长而无法存储在内存缓冲区中,则拒绝输入并显示错误消息并提示用户新输入。

下面的代码是一个使用fgets代替 的示例scanf,因为fgets更适合读取整行输入。然而,由于使用也不是那么容易,我在我自己创建的两个辅助函数中fgets使用。fgets我将这些函数称为get_line_from_userget_int_from_user。这是我调用的两个函数main。我不fgets直接从打电话main

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

//forward declarations
void get_line_from_user( const char prompt[], char buffer[], int buffer_size );
int get_int_from_user( const char *prompt );

#define NUM_STUDENTS 2
#define MAX_STRING_SIZE 200

int main( void )
{
    typedef struct
    {
        char* firstName;
        char* lastName;
        int rollNumber;

    } STUDENT;

    STUDENT* students = malloc( NUM_STUDENTS * sizeof *students );

    //fill student data from user
    for ( int i = 0; i < NUM_STUDENTS; i++)
    {
        students[i].firstName = malloc( MAX_STRING_SIZE );

        get_line_from_user(
            "Enter first name: ",
            students[i].firstName,
            MAX_STRING_SIZE
        );

        //shrink the allocated memory buffers to the
        //required size
        students[i].firstName = realloc(
            students[i].firstName,
            strlen( students[i].firstName ) + 1
        );

        students[i].lastName = malloc( MAX_STRING_SIZE );
        get_line_from_user(
            "Enter last name: ",
            students[i].lastName,
            MAX_STRING_SIZE
        );

        students[i].rollNumber =
            get_int_from_user( "Enter roll number: " );

        //shrink the allocated memory buffers to the
        //required size
        students[i].lastName = realloc(
            students[i].lastName,
            strlen( students[i].lastName ) + 1
        );
    }

    //print back the stored input
    for ( int i = 0; i < NUM_STUDENTS; i++)
        printf(
            "First Name: %s, Last Name: %s, Roll number: %d\n",
            students[i].firstName, students[i].lastName,
            students[i].rollNumber
        );

    return EXIT_SUCCESS;
}

//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. If
//the line is too long to fit in the buffer, then the function
//will automatically reprompt the user for input. On failure,
//the function will never return, but will print an error
//message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
    for (;;) //infinite loop, equivalent to while(1)
    {
        char *p;

        //prompt user for input
        fputs( prompt, stdout );

        //attempt to read one line of input
        if ( fgets( buffer, buffer_size, stdin ) == NULL )
        {
            printf( "Error reading from input!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to find newline character
        p = strchr( buffer, '\n' );

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small to store the entire line)
        if ( p == NULL )
        {
            int c;

            //a missing newline character is ok if the next
            //character is a newline character or if we have
            //reached end-of-file (for example if the input is
            //being piped from a file or if the user enters
            //end-of-file in the terminal itself)
            if ( (c=getchar()) != '\n' && !feof(stdin) )
            {
                if ( ferror(stdin) )
                {
                    printf( "Error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

                printf( "Input was too long to fit in buffer!\n" );

                //discard remainder of line
                do
                {
                    c = getchar();

                    if ( ferror(stdin) )
                    {
                        printf( "Error reading from input!\n" );
                        exit( EXIT_FAILURE );
                    }

                } while ( c != '\n' && c != EOF );

                //reprompt user for input by restarting loop
                continue;
            }
        }
        else
        {
            //remove newline character by overwriting it with
            //null character
            *p = '\0';
        }

        //input was ok, so break out of loop
        break;
    }
}

//This function will attempt to read one integer from the user. If
//the input is invalid, it will automatically reprompt the user,
//until the input is valid.
int get_int_from_user( const char *prompt )
{
    //loop forever until user enters a valid number
    for (;;)
    {
        char buffer[1024], *p;
        long l;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "Line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "Unrecoverable error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

            } while ( c != '\n' );

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        l = strtol( buffer, &p, 10 );
        if ( p == buffer )
        {
            printf( "Error converting string to number!\n" );
            continue;
        }

        //make sure that number is representable as an "int"
        if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
        {
            printf( "Number out of range error!\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6abc" gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return l;

    continue_outer_loop:
        continue;
    }
}
Run Code Online (Sandbox Code Playgroud)

该程序具有以下行为:

students[x].firstName=(char*)malloc(sizeof(char*));
Run Code Online (Sandbox Code Playgroud)

请注意,通常建议始终检查 的返回值malloc,因为否则,如果malloc由于某种原因失败,则程序将出现异常行为。我没有将这些检查添加到我的代码中,因为我不想分散我所做的其他更改的注意力。