结构指针C语言

时间:2019-05-22 16:30:16

标签: c string buffer-overflow

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

最初,我将'godiste'字段作为一个整数,并注意到它没有起作用,所以我将其更改为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);
}

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

1 个答案:

答案 0 :(得分:2)

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


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

话虽如此,您的问题似乎源于以下事实:年份字段是一个四个字符的数组。字符数组是C语言中的字符串,按照惯例,它们必须以n终止,以指示它们在哪里停止。由于您在只能容纳四个字符的空间中写入四个字符的值(年份),因此结果是程序实际上并未将第一个元素分配给前一个元素的最后一个元素;发生的事情是,当它读取结构的最后一个字段时,它没有找到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后输入缓冲区中的换行符是必需的。可能会引起注意。

希望这会有所帮助,伙计。我翻译了您的代码中的名称,如果Google翻译正确,则为克罗地亚语。如果是这样,那么恭喜您获得去年的决赛。祝你好运