c - C语言结构体指针

标签 c string buffer-overflow

我正在尝试将值加载到我的结构中,但它将每个新结构的第一个元素附加到前一个结构的最后一个元素。

最初,我将“godiste”字段设置为 int,并注意到它不起作用,因此我将其更改为 char,并注意到由于某种原因,我的输入函数“upis”附加了 igraci 的第二个成员的值“Ime”对应第一个成员的“godiste”。似乎无法弄清楚为什么会发生这种情况。

我在 main 中放置了一个 printf,它只打印 igraci 成员 0 的“godiste”,以确保这不是我编写的函数“ispis”的问题,而且它实际上使“godiste”的值成为“成员 0 的“godiste” + 成员 1 的“ime”。

'ime'是玩家名字 “prezime”是球员的姓氏 'pozicija' 是球员的位置 “godiste”是球员的出生年份

“igraci”是玩家列表。

程序首先询问您想要输入的玩家人数,然后询问您的详细信息并打印出您输入的内容。

#include <stdio.h>

struct futbaler
    {
        char ime[15];
        char prezime[20];
        char pozicija[15];
        char godiste[4];
    };

void upis(struct futbaler* pok, int n)
{
    int i;

    for(i=0; i<n;i++)
    {
        printf("Ime igraca #%d: ",i+1);
        scanf("%s",(pok+i)->ime);
        printf("Prezime igraca #%d: ",i+1);
        scanf("%s",(pok+i)->prezime);
        printf("Pozicija igraca #%d: ",i+1);
        scanf("%s",(pok+i)->pozicija);
        printf("Godiste igraca #%d: ",i+1);
        scanf("%s",(pok+i)->godiste);
    }
}

void ispis(struct futbaler* pok, int n)
{
    int i;

    for(i=0; i<n;i++)
    {
        printf("\nIme igraca #%d je: %s\n",i+1,(*(pok+i)).ime);
        printf("\nPrezime igraca #%d je: %s\n",i+1,(*(pok+i)).prezime);
        printf("\nPozicija igraca #%d je: %s\n",i+1,(*(pok+i)).pozicija);
        printf("\nGodiste igraca #%d je: %s\n",i+1,(*(pok+i)).godiste);
    }
}

main()
{
    int n;
    printf("Koliko bi igraca uneli? ");
    scanf("%d",&n);
    struct futbaler* pok;
    struct futbaler igraci[n];
    pok = igraci;
    upis(pok,n);
    ispis(pok,n);

    printf("%s",igraci[0].godiste);
}

我只是想弄清楚为什么以及如何将这些彼此无关的值附加在一起。

最佳答案

针对 David C. Rankin 指出的更正进行了编辑。


编译器需要在编译时知道数组的长度,以便知道要在堆栈上分配多少内存。否则,如果您按照现在的方式进行操作,即询问用户要创建多大的数组,然后对它进行堆栈分配,那么您将使用称为可变长度数组 (VLA) 的功能。出于安全原因,这不是一个好主意。

话虽如此,您的问题似乎源于您的年份字段是一个四字符数组这一事实。字符数组是 C 中的字符串,按照惯例,它们必须以 null 结尾以指示它们停止的位置。由于您在只能容纳四个字符的空间中写入四个字符的值(年份),因此结果是程序实际上并未将第一个元素分配给前一个元素的最后一个元素;而是将第一个元素分配给前一个元素。发生的情况是,当它读取结构体的最后一个字段时,它找不到 nul 终止符并直接读取它。因此,数组是一个连续的内存块,因此它将这两个字段视为一个。

最后,我强烈建议您使用更传统的方式来取消引用数组。 arr[i] 只是 *(arr + i) 的语法糖,但它更容易阅读。并不是说这是问题的根源,但任何能让你的生活更轻松的事情都可能是个好主意。


这是我对您的问题的实现。

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

#ifndef TRUE
enum { FALSE= 0, TRUE = !FALSE };
#endif

#define MAX_LINE_LENGTH 80

/* munch: remove newline from input buffer */
void munch(char* s) {
    size_t len = strlen(s);

    for (size_t i = len - 1; i >= 0; --i) {
        if (s[i] == '\n') {
            s[i] = '\0';
            break;
        }
    }
}

void strcpy_(char* s, const char* t) {
    while ((*s++ = *t++) != '\0')
        ;
}

char* strdup_(const char* s) {
    size_t len = strlen(s);
    char* str = malloc((len + 1) * sizeof (char));

    if (str == NULL) {
        fprintf(stderr, "[Error] Memory allocation failure\n");

        exit(EXIT_FAILURE);
    }

    return memcpy(str, s, len + 1);
}

enum position_t {
    POSITION_UNDEFINED,
    POSITION_STRIKER,
    POSITION_MIDFIELDER,
    POSITION_DEFENDER,
    POSITION_GOALKEEPER
};

const char* position_striker_strings[] = {
    "Striker",
    "striker",
    "False 9",
    "false 9"
};

const char* position_midfielder_strings[] = {
    "Midfielder",
    "midfielder",
    "Centerback",
    "centerback"
};

const char* position_defender_strings[] = {
    "Defender",
    "defender",
    "Fullback",
    "fullback"
};

const char* position_goalkeeper_strings[] = {
    "Goalkeeper",
    "goalkeeper",
    "Goalie",
    "goalie"
};

int position_read(const char* s) {
    for (size_t i = 0; i < (sizeof (position_striker_strings) / sizeof(const char*)); ++i) {
        if (strcmp(s, position_striker_strings[i]) == 0)
            return POSITION_STRIKER;
    }

    for (size_t i = 0; i < (sizeof (position_midfielder_strings) / sizeof(const char*)); ++i) {
        if (strcmp(s, position_midfielder_strings[i]) == 0)
            return POSITION_MIDFIELDER;
    }

    for (size_t i = 0; i < (sizeof (position_defender_strings) / sizeof(const char*)); ++i) {
        if (strcmp(s, position_defender_strings[i]) == 0)
            return POSITION_DEFENDER;
    }

    for (size_t i = 0; i < (sizeof (position_goalkeeper_strings) / sizeof(const char*)); ++i) {
        if (strcmp(s, position_goalkeeper_strings[i]) == 0)
            return POSITION_GOALKEEPER;
    }

    return POSITION_UNDEFINED;
}

char* position_str(int position) {
    switch (position) {
        case POSITION_STRIKER: {
            return "Striker";
        } break;

        case POSITION_MIDFIELDER: {
            return "Midfielder";
        } break;

        case POSITION_DEFENDER: {
            return "Defender";
        } break;

        case POSITION_GOALKEEPER: {
            return "Goalkeeper";
        } break;

        default: {
            return "Unknown Position Code";
        }
    }
}

struct player_t {
    char* first_name;
    char* last_name;
    int position;
    int year;
};

struct player_t* player_allocate() {
    struct player_t* player = calloc(1, sizeof(struct player_t));

    if (player == NULL) {
        fprintf(stderr, "[Error] Memory allocation failure\n");

        exit(EXIT_FAILURE);
    }

    return player;
}

struct player_t* player_new(const char* first_name, const char* last_name, int position, int year) {
    struct player_t* p = player_allocate();

    p->first_name = strdup_(first_name);
    p->last_name = strdup_(last_name);
    p->position = position;
    p->year = year;

    return p;
}

void player_print(struct player_t* player) {
    if (player == NULL)
        return;

    printf("\t%s, %s\n", player->last_name, player->first_name);
    printf("\t\tPosition: %s\n", position_str(player->position));
    printf("\t\tYear: %d\n", player->year);
}

void player_list_print(struct player_t** player_list, size_t n) {
    if (player_list == NULL)
        return;

    printf("\n\nPlayer List: \n\n");

    for (size_t i = 0; i < n; ++i) {
        if (player_list[i] == NULL)
            continue;

        player_print(player_list[i]);
    }

    printf("\n\n");
}

void clear_buffer(char* buffer, size_t n) {
    memset(buffer, 0, n * sizeof(char));
}

int main(void)
{
    char input_buffer[MAX_LINE_LENGTH];
    clear_buffer(input_buffer, MAX_LINE_LENGTH);

    printf("How many players would you like to enter? ");
    fgets(input_buffer, MAX_LINE_LENGTH, stdin);

    errno = 0;
    char* endptr = NULL;
    long n = strtol(input_buffer, &endptr, 10);

    if ((errno == ERANGE && (n == LONG_MAX || n == LONG_MIN)) || (errno != 0 && n == 0)) {
        perror("strtol");

        return EXIT_FAILURE;
    }

    if (endptr == input_buffer) {
        fprintf(stderr, "No digits were found\n");

        return EXIT_FAILURE;
    }

    printf("Enter %ld player(s).\n", n);

    struct player_t** player_list = calloc(n, sizeof (struct player_t *));

    for (size_t i = 0; i < (size_t) n; ++i) {
        player_list[i] = player_allocate();

        printf("\nFirst Name: ");
        clear_buffer(input_buffer, MAX_LINE_LENGTH);
        fgets(input_buffer, MAX_LINE_LENGTH, stdin);
        munch(input_buffer);
        player_list[i]->first_name = strdup_(input_buffer);

        printf("Last Name: ");
        clear_buffer(input_buffer, MAX_LINE_LENGTH);
        fgets(input_buffer, MAX_LINE_LENGTH, stdin);
        munch(input_buffer);
        player_list[i]->last_name = strdup_(input_buffer);

        printf("Position: ");
        clear_buffer(input_buffer, MAX_LINE_LENGTH);
        fgets(input_buffer, MAX_LINE_LENGTH, stdin);
        munch(input_buffer);
        player_list[i]->position = position_read(strdup_(input_buffer));

        printf("Year: ");
        clear_buffer(input_buffer, MAX_LINE_LENGTH);
        fgets(input_buffer, MAX_LINE_LENGTH, stdin);
        munch(input_buffer);
        player_list[i]->year = atoi(input_buffer);
    }

    player_list_print(player_list, n);

    return EXIT_SUCCESS;
}

程序执行:

How many players would you like to enter? 2
Enter 2 player(s).

First Name: Christiano 
Last Name: Ronaldo
Position: Striker
Year: 1985

First Name: Lionel
Last Name: Messi
Position: striker
Year: 1986


Player List: 

    Ronaldo, Christiano
        Position: Striker
        Year: 1985
    Messi, Lionel
        Position: Striker
        Year: 1986

您会注意到我编写了自己的 strdup_strcpy_ 函数。我认为了解它们是如何实现的会很有趣,并且我还为许多换行符和制表符添加了一些功能。这是因为我不喜欢使用 scanf,因此我还在本示例中使用了 atoistrtol

您还会注意到,strtol 涉及大量错误检查,而 atoi 则没有;这正是using atoi is not recommended的原因.

关于实现,由于列表中的玩家数量在编译时是不确定的,我使用了一个名为 player_list 的双指针来动态分配每个玩家。为此,您必须首先分配 player_list 指针本身,然后迭代每个指针,依次单独分配每个玩家结构。

我还使用了位置的整数值和有效位置值的枚举。当读取用户输入时,我然后检查匹配的有效位置字符串,仅在确实存在匹配时添加特定位置。这是出于数据验证的目的,因此用户不能简单地添加新职位;那将是数据库管理员的工作。相反,如果未找到匹配项,则将玩家位置简单地设置为 POSITION_UNDEFINED

有效位置字符串的数量是根据数组中的位置数量计算的,因此您可以自由地在每个位置数组中添加更多有效字符串,而不必担心更改位置字符串匹配代码。


如果您只是想要一个基本的实现,您实际上可以稍微修改您的代码来处理年份/终止符问题,并且可能将名称字段更改为指针以处理任何大小的名称。就目前情况而言,您很容易受到堆栈溢出的影响。我没有仔细研究您的输入机制,但我很确定您的输入中应该有多余的换行符,因为调用 scanf 后输入缓冲区中存在换行符。可能需要注意一些事情。

希望这有帮助,伙计。我翻译了你代码中的名字,如果谷歌翻译正确,那就是克罗地亚语。如果这是真的,那么恭喜你去年进入了决赛。祝你好运

关于c - C语言结构体指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56261333/

相关文章:

c - 将数组中的两个相邻字符存储到一个int中

android - 编译 ARM64 android 内核时出错

c - 读取文本文件,将每一行分成单独的数组并在 C 中排序

ios - 在 NSString 中查找字符(#)的最后一个索引(位置)

Java:使用定界符摆脱大写字母单词?

c - 我正在尝试创建一个打开文件以在 C 中读取的函数

循环时无法比较数组上的字符串

c - 如何将任意字节发送到 gdb 中程序的 STDIN?

c - 缓冲区溢出,用 snprintf 代替 char 调整大小?

security - 《剥削的艺术》一书中的缓冲区溢出示例