1

Тема: Масив вказівників на символьні рядки

Маю завдання, де потрібно вводити декілька речень і виділяти слова, які починаються заданою літерою. Для зберігання символьних рядків я вирішив використати не багатовимірний масив, а масив вказівників. Не можу розібратися чому gets не заносить рядок в пам'ять. Де я допускаю помилку?

int k = 0, sym;
    char* arr[MAX], *symbols = " .,-;:()", *ps;
    for (; k < MAX; k++) {
        printf("Введіть %d речення: ", k + 1);
        if (*gets(arr[k]) == NULL) {
            break;
        }
    }

2

Re: Масив вказівників на символьні рядки

Компілятор усе підказує.

Наприклад, gets, а не *gets.

Також компілятор має видати попередження:

<source>:10: warning: the `gets' function is dangerous and should not be used.

Подивіться в бік fgets.

І гляньте в бік декларації symbols, там не все гаразд.

3

Re: Масив вказівників на символьні рядки

Наприклад, gets, а не *gets.

Знаю, що компілятор визначає це як помилку, але ввід зупиняється лише з використанням розіменування. Не знаю як зупинити іншим способом, окрім того, що ввести кількість речень зазделегіть.

Також компілятор має видати попередження:

<source>:10: warning: the `gets' function is dangerous and should not be used.

Щодо цієї помилки, то вона вирішується використанням gets_s.

Подивіться в бік fgets.

Дякую за пораду, але, наскільки я розумію, це лише аналог gets_s і його використання не вирішує моєї проблеми...

І гляньте в бік декларації symbols, там не все гаразд.

Підскажіть, будь ласка, що там не так?

4

Re: Масив вказівників на символьні рядки

Основна проблема тут у тому, що вказівник на char - це саме вказівник. Стрілочка, яка кудись вказує. Адреса в пам'яті. Але окрім вказівника, треба ще цю пам'ять виділити - глобально, статично чи локально, проголосивши змінну (масив), або ж динамічно, функціями calloc чи malloc, і присвоївши вказівник на цю пам'ять arr[k]. Без цього arr[k] буде вказувати казна-куди, і при спробі доступу до даних за адресою arr[k] буде ставатися казна-що (невизначена поведінка, undefined behavior, UB). Тому виділяйте пам'ять.
Функція gets, так, є небезпечною, бо не обмежує запис у пам'ять і тобто якщо ви намагаєтеся писати у виділену пам'ять, але символів на вході більше, ніж пам'яті виділено, gets буде писати за межі виділеної пам'яті, що, знову ж таки, є UB. Крім того, gets у випадку помилки повертає NULL, а ви намагаєтеся розіменовувати те, що gets повертає, і порівнювати з NULL. Не треба так.

Подякували: Foxy4, bebyk, leofun013

5

Re: Масив вказівників на символьні рядки

Foxy4 написав:
Foxy4 написав:

І гляньте в бік декларації symbols, там не все гаразд.

Підскажіть, будь ласка, що там не так?

Це, насправді, дрібниця порівняно із виділенням пам'яті.
" .,-;:()" - це стрічковий літерал, який заноситься до виконуваного файла і в сучасних компіляторах його можна лише читати.
Ваше проголошення symbols допускає, що можна зробити щось на кшталт symbols[0]='!'; - а це призведе до запису в захищену пам'ять і, наскільки я пам'ятаю, UB - а може і просто до аварійного завершення, можу плутати, в будь-якому разі не слід писати в symbols, а щоб цього уникнути - треба проголосити його як const char*.

6

Re: Масив вказівників на символьні рядки

koala написав:

Основна проблема тут у тому, що вказівник на char - це саме вказівник. Стрілочка, яка кудись вказує. Адреса в пам'яті. Але окрім вказівника, треба ще цю пам'ять виділити - глобально, статично чи локально, проголосивши змінну (масив), або ж динамічно, функціями calloc чи malloc, і присвоївши вказівник на цю пам'ять arr[k]. Без цього arr[k] буде вказувати казна-куди, і при спробі доступу до даних за адресою arr[k] буде ставатися казна-що (невизначена поведінка, undefined behavior, UB). Тому виділяйте пам'ять.
Функція gets, так, є небезпечною, бо не обмежує запис у пам'ять і тобто якщо ви намагаєтеся писати у виділену пам'ять, але символів на вході більше, ніж пам'яті виділено, gets буде писати за межі виділеної пам'яті, що, знову ж таки, є UB. Крім того, gets у випадку помилки повертає NULL, а ви намагаєтеся розіменовувати те, що gets повертає, і порівнювати з NULL. Не треба так.

Дякую за роз'яснення, але не розумію як тоді зупинити цикл, коли gets_s повертає NULL?

7

Re: Масив вказівників на символьні рядки

Foxy4 написав:

Підскажіть, будь ласка, що там не так?

Я ваш код закинув ув онлайн-компілятора і просто виправив помилки. Вийшло отак:

#include <stdio.h>

int main() {
    const int MAX = 100;
    int k = 0;
    char* arr[MAX];
    const char* symbols = " .,-;:()";
    for (; k < MAX; k++) {
        printf("Введіть %d речення: ", k + 1);
        if (gets(arr[k]) == NULL) {
            break;
        }
    }
    return 0;
}

Але ви краще послухайте koala, він фахівець.

8

Re: Масив вказівників на символьні рядки

bebyk написав:
Foxy4 написав:

Підскажіть, будь ласка, що там не так?

Я ваш код закинув ув онлайн-компілятора і просто виправив помилки. Вийшло отак:

#include <stdio.h>

int main() {
    const int MAX = 100;
    int k = 0;
    char* arr[MAX];
    const char* symbols = " .,-;:()";
    for (; k < MAX; k++) {
        printf("Введіть %d речення: ", k + 1);
        if (gets(arr[k]) == NULL) {
            break;
        }
    }
    return 0;
}

Але ви краще послухайте koala, він фахівець.

Дякую, але вже усе виправив)

9

Re: Масив вказівників на символьні рядки

Foxy4 написав:

Дякую за роз'яснення, але не розумію як тоді зупинити цикл, коли gets_s повертає NULL?

А що саме ви збираєтеся робити, щоб gets_s повертало NULL?

10

Re: Масив вказівників на символьні рядки

koala написав:
Foxy4 написав:

Дякую за роз'яснення, але не розумію як тоді зупинити цикл, коли gets_s повертає NULL?

А що саме ви збираєтеся робити, щоб gets_s повертало NULL?

Вводити порожній рядок

11 Востаннє редагувалося koala (09.02.2023 23:38:01)

Re: Масив вказівників на символьні рядки

А чому саме ви вирішили, що порожній рядок призводить до цього? У документації я такого не бачу. Порожній рядок - це просто '\0' у стрічку і поверне стрічку.
Хоча, оскільки ви розіменовували те, що повертає gets, то вийде умова

'\0' == NULL

а оскільки NULL при порівняннях із числами перетворюється на 0, то все пройде нормально. Тобто ви не розіменовували неправильно (хоча як сказати...), а NULL вжили неправильно, там мав бути '\0'.
Коротше, якщо повністю безпечно - то десь так

char *ret = gets_s(arr[k], 100); //100 - умовно
if(ret==NULL) {
    //обробка помилки
} else if(*ret=='\0') {
    //обробка порожньої стрічки
} else {
    //обробка того, що ввели
}
Подякували: bebyk, Foxy4, leofun013