询问malloc for(char*)

fdbelqdn  于 2023-06-21  发布在  其他
关注(0)|答案(2)|浏览(78)

我只是想知道为什么

students[x].firstName=(char*)malloc(sizeof(char*));

这个不需要字符串的长度。
完整代码(来自互联网上的某个地方):

#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);
}

解释:

students[x].firstName=(char*)malloc(sizeof(char*));
polhcujo

polhcujo1#

根据您使用的平台,线路

students[x].firstName=(char*)malloc(sizeof(char*));

很可能相当于

students[x].firstName = malloc( 4 );

students[x].firstName = malloc( 8 );

因为这是大多数平台上char *(指向char的指针)的大小。
这意味着如果用户输入多于3或7个字符(包括终止空字符在内的4或8个字符),则行

printf("Enter first name :");
scanf("%s",students[x].firstName);

将导致buffer overflow,这将调用undefined behavior。这意味着您的程序可能会崩溃或以其他方式行为不当。
因此,你的担心是有道理的。那条线

students[x].firstName=(char*)malloc(sizeof(char*));

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

students[x].firstName = malloc( 200 );

那么你的程序仍然是不安全的,因为用户仍然可以通过输入一个长度至少为200个字符的单词来引起缓冲区溢出。因此,更安全的做法是写

scanf( "%199s", students[x].firstName );

而不是:

scanf("%s",students[x].firstName);

这将把scanf写入内存缓冲区的字符数限制为200(199个匹配字符加上终止空字符)。
然而,即使这个解决方案更好,它仍然不是理想的,因为如果输入太长,scanf将默默地截断输入,并将剩下的行留在输入流上。这意味着下次在输入流上调用scanf时,这个剩余的输入可能会引起麻烦,因为这是首先读取的内容。
由于这个原因,最好总是读取整行输入,如果它太长而不能存储在内存缓冲区中,则用错误消息拒绝输入并提示用户输入新的内容。
下面的代码中有一个使用fgets而不是scanf的例子,因为fgets更适合阅读整行输入。然而,由于使用fgets也不是那么容易,所以我在自己创建的两个辅助函数中使用了fgets。我将这些函数称为get_line_from_userget_int_from_user。这是我从main调用的两个函数。我不直接从main调用fgets

#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;
    }
}

此程序具有以下行为:

Enter first name: This line is longer than 200 characters. This line is longer than 200 characters. This line is longer than 200 characters. This line is longer than 200 characters. This line is longer than 200 characters. 
Input was too long to fit in buffer!
Enter first name: John
Enter last name: Doe
Enter roll number: 8
Enter first name: Jane
Enter last name: Doe
Enter roll number: 9
First Name: John, Last Name: Doe, Roll number: 8
First Name: Jane, Last Name: Doe, Roll number: 9

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

rsaldnfx

rsaldnfx2#

@Andreas Wenzel很好地回答了OP代码的错误。
最好避免使用"%s",因为它不能处理名字或姓氏中的空格。
考虑放弃所有scanf()的使用,转而使用fgets()
建议使用辅助函数读取名称的替代代码。helper函数中有一些弱点,但根据编码目标,可以很容易地进行改进。还建议一个同样的助手函数来读取学生人数。

// https://www.indiatimes.com/news/weird/five-incredibly-long-names-in-the-world-and-the-not-soordinary-people-who-have-them-243597.html
#define NAME_SIZE_MAX (1000 + 1 /* '\0' */)

char *read_name_alloc(const char * prompt) {
  if (prompt) fputs(prompt, stdout);
  char buf[NAME_SIZE_MAX + 1]; // 1 more for `\n`.
  if (fgets(buf, sizeof buf, stdin) == NULL) {
    return NULL;
  }
  // Lop off terminating null character.
  buf[strcspn(buf, "\n\r")] = '\0';
  return strdup(buf);
}

...

for (x = 0; x < numStudents; x++) {
  // Error checking omitted for brevity. 
  students[x].firstName = read_name_alloc("Enter first name :");
  students[x].lastName = read_name_alloc("Enter last name :");
  students[x].rollNumber = read_int("Enter roll number  :", 0 /* man */, INT_MAX /* max */);
}
   
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);
}

相关问题