Я пытаюсь увеличить указатель через массив структур — мое решение работает, но делает это не интуитивно понятным способом

#c #pointers #struct

Вопрос:

Итак, я поместил всю свою «отладку printf» в код, чтобы показать, о каком эффекте я говорю. Я вложил их в многострочные комментарии /* со звездными баннерами, чтобы легко идентифицировать их, если вы хотите запустить программу и просто удалить/удалить блоки кода, поскольку они не служат никакой другой цели, кроме объяснения того, что я пытаюсь сказать.

Предыстория: Я пытался создать игру в крестики-нолики, и я почти закончил, но, похоже, не могу понять, почему увеличение указателя на массив структур на размер этой структуры не перемещается на один индекс после каждого приращения, а вместо этого перемещается на 3. Ниже приведен код до сих пор.

По сути, адрес остается прежним, указатель указывает на правильные места, все работает, но я не понимаю логики в строке 186, ближе к самому концу программы в области действия функции locationfinder. Я не понимаю, почему увеличение указателя на размер структуры (который содержит один индекс в массиве) не работает, и мне приходится прибегать к увеличению на^, которое делится на 3…

 #include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define height 3
#define width 3

//STRUCT DEFINITIONS--
struct square //the size of a struct of type square is 8 bytes (this is important info for the issue)
{
    char input;
    int location;
};

struct playermoves //unnecessary struct but ignore
{
    struct square array[height][width];

}table;

//PROTOTYPES--
void displaytable(void); //displays the table
void script1(void); //prints the instruction script
void game(int count); //runs the game operations
char * locationfinder(int * locationpointer, int reference); //cross-checks the chosen tic-tac-toe table location a player
                                                            //inputs by storing as "int reference"
//winchecker(int loc); - function not yet written



int main(void)
{
    //below is a loop filling table with 'c' for 'clear' and adding locations as integers assigned to each cell
    int locations = 1;
    for(int i = 0; i < height; i  )
    {
        for(int j = 0; j < width; j  )
        {
            table.array[i][j].input = 'c';
            table.array[i][j].location = locations;
            locations  ;
        }
    }
    printf("TIC TAC TOEn");
    //sleep(2);
    printf("nnn Players, this is your table. It is set to clear right now, using the character 'c'n");
    displaytable();
    //sleep(7);
    printf("nn");

    int turn = 1;

    script1();
    //below runs through the maximum moves (9) that could be made in a game with a 9 squared table
    //and decides who's turn it is on each iteration
    int allsquares = height*width;
    for(int i = 1; i < (allsquares 1); i  ) //setting i to 1, instead of zero, for the modulus "turn" decider to work
    {
        if(i%2 == 0)
        {
            turn = 2;
        }
        else
        {
            turn = 1;
        }
        game(turn);
    }

}

void script1(void)
{
    printf("Player 1, your character is alphabetical 'X'n");
    //sleep(3);
    printf("Player 2, your character is alphabetical 'O'n");
    printf("_n_n");
    //sleep(5);
    printf("You can see the table at any time by typing 'd' on your turn, in lower casen");
    printf("_n_n");
    //sleep(3);
    printf("Are you both ready? Game start!n");
    //sleep(3);
}

void game(int turn)
{
    int move;
    if(turn == 1) //player one's round
    {
        do
        {
            char play = '';
            move = 0;
            printf("====================================================================================================n");
            printf("Player 1, what is your move?nInput the location of the square you want to put your counter X into.n");

            scanf("%i", amp;move); //scanning for a location (integer) input
            scanf("%c", amp;play); //scanning for a character shortcut, 'd', which displays the table
            if(play == 'd')
            {
                displaytable();
                sleep(2);
            }
        }while(move == '' || move < 1 || move > 9); //if move is empty, or out of bounds of locations available on table, repeat prompt

        /**********************************************************************************************************************
        printf("nnPRINTF DEBUGGING: n(these addresses should correspond with those referenced below in the locationfinder scope):n");
        printf("SCOPE: mainn"); 
        for(int a = 0, counter = 1; a < height; a  )
        {
            for(int b = 0; b < width; b  )
            {
                    printf("Location %i of cell %i: is %pn", counter, counter, amp;table.array[a][b].location);
                    counter  ;
            }
        }
        ***************************************************************************************************************/

        //address returned in the form of 'char * locationfinder' to char input field of array location that user inputted
        char * editor = locationfinder(amp;table.array[0][0].location, move);
        *editor = 'X'; //value modified by editor pointer to reflect player's move

    }
    else if(turn == 2)
    {
        do
        {
            char play = '';
            move = 0;
            printf("====================================================================================================n");
            printf("Player 2, what is your move?nInput the location of the square you want to put your counter O into.n");

            scanf("%d", amp;move); //scanning for a location (integer) input
            scanf("%c", amp;play); //scanning for a character shortcut, 'd', which displays the table
            if(play == 'd')
            {
                displaytable();
                sleep(2);
            }
        }while(move == '' || move < 1 || move > 9); //if move is empty, or out of bounds of locations available on table, repeat prompt

        //address returned in the form of 'char * locationfinder' to char input field of array location that user inputted
        char * editor = locationfinder(amp;table.array[0][0].location, move);
        *editor = 'O'; //value modified by editor pointer to reflect player's move
    }
    //winchecker function to go here
}

void displaytable(void)
{
    printf(" ========= ========= ========= n");
    printf("|1.  %c    |4.   %c    |7.  %c   |n", table.array[0][0].input, table.array[1][0].input, table.array[2][0].input);
    printf(" --------- --------- --------- n");
    printf("|2.  %c    |5.   %c    |8.  %c   |n", table.array[0][1].input, table.array[1][1].input, table.array[2][1].input);
    printf(" --------- --------- --------- n");
    printf("|3.  %c    |6.   %c    |9.  %c   |n", table.array[0][2].input, table.array[1][2].input, table.array[2][2].input);
    printf(" ========= ========= ========= n");
}

//locationfinder below contains loop which cross-checks the user inputted location (integer)
//with the value at the .location member field of each 'square' array

char * locationfinder(int * locationpointer, int reference)
{
    /****************************************************************************************************************************
    printf("nnThe location pointer  in the locationfinder scope should display the same addresses as those in the loop in Main.n");
    printf("As you can see above, these addresses (of the location member fields in the array) increment in hexadecimal by 8 bytesn");
    printf("As you will see below, the incrementation of the pointer in locationfinder scope does so too**n");
    printf("This is because sizeof(struct square) also returns 8 bytes: %lun", sizeof(struct square));
    printf("**^So why must the incrementation for this pointer in the loop below be set to sizeof(struct square)/3 to work?n");
    
    printf("nSCOPE: locationfinder.n");
    ************************************************************************************************************************/
    int runtime = height*width;
    for(int i = 0; i < runtime; i  )
    {
        /**************************************************************************************************
        printf("Location %i at cell %i: is %pn", i 1, i 1, locationpointer);
        **************************************************************************************************/

        if(*locationpointer == reference)
        {
            char * play = (char*)locationpointer-sizeof(int); //moving the pointer to the char input field
            return play; //returning this pointer
        }
        locationpointer  = sizeof(struct square)/3; //not locationpointer = sizeof(struct square) ???
        // it should increment with sizeof(struct square) properly, but it doesn't
    }
    //disregard below
    char m = 'm';
    char * zero = amp;m;
    return zero;
}
 

Комментарии:

1. Увеличьте указатель на 1 , чтобы перейти к следующему элементу. Точно так же, как с индексированием массива. Вам не нужно «увеличивать указатель на размер структуры», если указатель имеет правильный тип.

2. В стороне: scanf("%c", amp;play); вам наверняка понадобится такое пространство, как это: scanf(" %c", amp;play);

3. Также return zero приводит к неопределенному поведению: возвращению адреса локальной переменной.

4. @Флюгер спасибо за исправления 🙂 Хотя я немного сбит с толку. Я попытался запустить функцию поиска местоположения, не получив ничего, кроме инструкции return в if-ветви цикла «я», и это меня огорчило, поэтому я просто что-то добавил в конце, чтобы остановить сообщения об ошибках (плохо, я знаю). Что бы я сделал вместо этого в таком случае? Также, почему пространство в сканфе?

5. scanf Преобразование останавливается на первом символе, который оно не может преобразовать, который обычно (но не обязательно) является пробелом или новой строкой, и этот символ остается во входном буфере. Это будет прочитано следующим scanf() . Спецификаторы формата %d и %s и %f автоматически фильтруют такие начальные пробелы, но %c и %[] и %n не делают этого. Вы можете дать указание scanf сделать это, добавив пробел непосредственно перед % .

Ответ №1:

Если p является указателем на некоторый тип T и n является целым числом, p n указывает на n t-й элемент массива объектов типа T, начиная с p . В частности, p 1 это следующий элемент массива. Арифметика указателей работает с указателями, а не с адресами.

p n указывает на p[n] . На самом деле, именно так p[n] и определяется: это более приятный способ письма *(p n) .

Так locationpointer = sizeof(struct square)/3 работает, потому что в вашей системе, sizeof(struct square) оказывается, 3. Правильный способ написания этого

 locationpointer  = 1;
 

или

 locationpointer  ;
 

или

   locationpointer;
 

Строка выше char * play = (char*)locationpointer-sizeof(int); также неверна, но она работает на вашей платформе. (char*)locationpointer-sizeof(int) указывает sizeof(int) элементы ранее (char*)locationpointer . Поскольку (char*)locationpointer является указателем на 1 и должно быть равно 1, это указывает на адрес, указанный ранее . char sizeof(char) sizeof(int) locationpointer Пока все хорошо.

Проблема в том, что на некоторых платформах это не адрес location поля предыдущего struct square ! Большинство платформ требуют , чтобы an int хранился в памяти по адресу, кратному sizeof(int) этому, или, по крайней мере, существует снижение производительности, если это не так. Это требование называется выравниванием. Из-за требований к выравниванию компиляторы могут и часто делают заполнение в структурах. Заполнение может появляться как между полями, так и в конце структуры.

На многих платформах int должен быть четный адрес, который требует заполнения перед location полем. На нескольких платформах int могут быть выровнены, но некоторые другие типы (указатели, поплавки) требуют выравнивания (обычно 4 или 8), и, как следствие, все структуры имеют размер, кратный 4 или 8, что требует заполнения в конце структуры. С отступом в конце, (char*)locationpointer-sizeof(int) указывает не на location поле, а немного дальше в памяти.

Правильный способ написания этой строки — взять адрес элемента структуры.

 #include <stddef.h>
…
int* play = amp;(locationpointer-1)->location;
 

Вы также должны переодеться locationfinder , чтобы вернуться int* . Нет смысла присваивать значение a char* .

Комментарии:

1. sizeof(struct square)/3 было бы 2 на любом разумном современном настольном компиляторе. …что такое чистое (плохое?) удача, случается, дает следующий квадрат при добавлении к an int * . Имейте в виду, что большая часть арифметики указателей, выполняемой OP, является неопределенным поведением.

Ответ №2:

Если у вас есть указатель на элемент структуры, вы можете найти указатель на владение struct следующим образом:

  struct square * get_square(int *locationptr)
 {
    return (struct square *) ((char *)locationptr - offsetof(struct square, location));
 }
 

Но если вы хотите выполнить поиск структуры в массиве на основе значения элемента, лучше передать указатель на первый элемент массива и искать элемент из него, а не наоборот, передавая указатель на элемент первого элемента и пытаясь вычислить указатель на структуру.

 struct square *locationfinder(struct square squares[height][width], int reference)
{
   for (int l = 0; l < height; l  )
     for (int c = 0; c < width; c  )
        if (squares[l][c].location == reference)
           return amp;(squares[l][c]);
   return NULL;
}
 

Но поскольку значение location может быть вычислено на основе позиции в массиве, нам действительно не нужно обращаться к его члену для выполнения поиска.

  /*
    `label` is a number 1..9 referencing a square on the board.
    we call it label rather than index, because indexes are *always*
    zero based in C
 */
 struct square *label2square(struct square squares[height][width], int label)
 {
     /* We need something zero-based for calculation */
     int index = label - 1;
     if ((index < 0) || (index >= width*height))
        return NULL;

     int line = index/width;
     int column = index%width;
     return amp;(squares[line][column]);
 }
 

Делая это, я также не вижу никакой необходимости в location члене struct square .

Просто добавленный комментарий, в вашей функции отображения есть ошибка, она использует array[column][line] , а не array[line][column]

Комментарии:

1. Спасибо!!! ваш ответ действительно здравомыслящий и красивый на вид. Я могу только надеяться, что со временем мои решения станут такими же эффективными, как и ваши. Чтение этого похоже на то, как будто кто-то открывает тебе глаза после того, как у тебя было туннельное зрение, лол, потому что ты права, на самом деле мне не нужно поле определения местоположения, и это меня шокирует. Кроме того, я никогда бы не подумал, что строка и столбец будут рассчитываться таким образом! Еще раз благодарю вас за объяснение того, что я делал неправильно до сих пор здесь и в ваших ответах, и за предоставление другой точки зрения. Я думаю, что мне нужно прочитать об арифметике указателей и о том, как это правильно делать.