1

Тема: Чому локальний string не знищується по закінченню функції

Привіт. Останнім часом вивчаю С.

Доволі затягнуло, цікава мова. Однак часом не зовсім можу зрозуміти логіку роботи string.

Іноді вдається накачити більш-менш адекватні пояснення. А от в одному випадку нічого не знайшов, тому вирішив спитати з конкретним прикладом.

Завдання з SoloLearn, називається Pig Latin. Суть така, що вводиться речення (string слів через пробіл), а програма має взяти кожне слово, перекинути першу літеру накінець і додати "ay", вивести перероблені слова в тому ж порядку.

Я завдання виконав:

#include <stdio.h>
#include <string.h>

char* latinize(char *word);

int main() {
    char sentence[100];
    char *token, *word;
    const char delimiter[2] = " ";
    fgets(sentence, 100, stdin);
    sentence[strcspn(sentence, "\n")] = 0;

    token = strtok(sentence, delimiter);

    while( token != NULL ) {
      word = token;
      printf("%s ", latinize(word));

      token = strtok(NULL, delimiter);
    }

    return 0;
}

char* latinize(char *word) {
    static char latinized_word[50];
    int len = strlen(word);

    char buffer[50] = "";
    char first;
    for (int i = 0; i < len; i++) {
        if (!i) {
          first = word[i];
        } else {
          strncat(buffer, &word[i], 1);
        }
    }
    strncat(buffer, &first, 1);
    strcat(buffer, "ay");

    strcpy(latinized_word, buffer);
    return latinized_word;
}

Я розумію, чому не знищується latinized_word по закінченні функції (і кожне попереднє «латинізоване» слово додається), воно статичне.

Проте не розумію, чому коли я не «очищу» локальний string buffer (char buffer[50] = "";), то відбувається те саме, що зі статичною latinized_word. Тобто додає кожне попереднє слово.

Приміром.

input:

thing to keep in mind is that

годящий output:

hingtay otay eepkay niay indmay siay hattay

output якщо я вкажу просто char buffer[50]; без "" —

hingtay hingtayotay hingtayotayeepkay hingtayotayeepkayniay hingtayotayeepkayniayindmay hingtayotayeepkayniayindmaysiay hingtayotayeepkayniayindmaysiayhattay

Напевно це через те, що string це array, а array це pointer. Але хіба локальний пойнтер не очищується?

Якщо хтось пояснить на пальцях у такому стилі, буду вдячний.

Подякували: ch0r_t1

2 Востаннє редагувалося wander (16.01.2021 02:01:46)

Re: Чому локальний string не знищується по закінченню функції

bebyk написав:

output якщо я вкажу просто char buffer[50]; без "" —

hingtay hingtayotay hingtayotayeepkay hingtayotayeepkayniay hingtayotayeepkayniayindmay hingtayotayeepkayniayindmaysiay hingtayotayeepkayniayindmaysiayhattay

Тому що використання неініціалізованих змінних/масивів (тобто елементів масиву), як от char buffer[50]; без "" — UB. В пам'яті буде мусор, компілятор не зобов'язаний давати ніякої діагностики, а результат виконання такої програми може бути яким завгодно. Те, що воно у вас не падає вже добре.

bebyk написав:

Напевно це через те, що string це array

В Сі немає string, ваш буфер це просто масив символів тому я б краще його так і називав, щоб в майбутньому не було плутанини зі string, як класом.

bebyk написав:

а array це pointer

Ні. Це не так, array != pointer.

Подякували: bebyk, lucas-kane, leofun013

3

Re: Чому локальний string не знищується по закінченню функції

Ну Ви трохи намудрували з кодом. Але то таке... Буває
По перше ви працюєте із функціями бібліотеки string.h в них вже програмно закладено працювати із масивами дами а ви їм чомусь передаєте символьні значення. не зрозуміло. якщо ви копіюєте до буферу посимвольно то краще використовувати оператор присвоєння, тобто

strncat(buffer, &word[i], 1);

замінимо на

buf[i] = word[i]....

По друге, це не стосується до помилки,

    for (int i = 0; i < N; ++i)
        if (!i) /*...*/ else  /* ... */

- так ніколи не робіть

Ось тримайте:

char* latinize(char* word)
{
    static char latinized_word[50];
    int len = strlen(word);

    char buffer[50];
    char first = *word;
    int i;
    for (i = 0; i < (len - 1); i++)
        buffer[i] = word[i + 1];
    buffer[i] = first, buffer[i + 1] = '\0';

    strcat(buffer, "ay");

    strcpy(latinized_word, buffer);
    return latinized_word;
}

Має бути, щось типу такого... Передивіться прототипи функцій бібліотеки string
http://www.cplusplus.com/reference/cstring/

Подякували: bebyk1

4

Re: Чому локальний string не знищується по закінченню функції

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

а array це pointer

Ні. Це не так, array != pointer.

Ім'я масиву є вказівником на ділянку пам'яті, так само як і перший елемент масиву, а точніше його адреса...
Частково це і є вказівник, конкретніше його окремий випадок. Тобто коли вказівнику присвоюється певний блок пам'яті (Для цього випадку у СТЕКУ)

Подякували: bebyk1

5 Востаннє редагувалося wander (16.01.2021 10:45:05)

Re: Чому локальний string не знищується по закінченню функції

lucas-kane написав:
wander написав:
bebyk написав:

а array це pointer

Ні. Це не так, array != pointer.

Ім'я масиву є вказівником на ділянку пам'яті, так само як і перший елемент масиву, а точніше його адреса...
Частково це і є вказівник, конкретніше його окремий випадок. Тобто коли вказівнику присвоюється певний блок пам'яті (Для цього випадку у СТЕКУ)

Це ви собі так вигадали і видаєте бажане за дійсне? Бо з дійсністю це має вкрай мало спільного.
До ознайомлення - тиць

Подякували: ch0r_t, lucas-kane2

6 Востаннє редагувалося ch0r_t (16.01.2021 12:36:29)

Re: Чому локальний string не знищується по закінченню функції

А по чому вивчаєте, коли можна запитати? Канонічний підручник чи українська книга?

Прихований текст
bebyk написав:

Привіт. Останнім часом вивчаю С.

# Користувач Bjarne42 покинув чат.

bebyk написав:

Доволі затягнуло, цікава мова.

# Користувач macro_IsAwesome1979 доєднався до чату.

7

Re: Чому локальний string не знищується по закінченню функції

tchort написав:

А по чому вивчаєте, коли можна запитати? Канонічний підручник чи українська книга?

Прихований текст
bebyk написав:

Привіт. Останнім часом вивчаю С.

# Користувач Bjarne42 покинув чат.

bebyk написав:

Доволі затягнуло, цікава мова.

# Користувач macro_IsAwesome1979 доєднався до чату.

По застосунку SoloLearn. Там і інтерактивний туторіал, і «батли» по знанню мови, і отакі от завдання.

Подякували: ch0r_t1

8

Re: Чому локальний string не знищується по закінченню функції

lucas-kane написав:

Ну Ви трохи намудрували з кодом. Але то таке... Буває

Дякую за приклад гарного коду, це мені дуже корисно!

lucas-kane написав:

По перше ви працюєте із функціями бібліотеки string.h в них вже програмно закладено працювати із масивами дами а ви їм чомусь передаєте символьні значення. не зрозуміло.

Мало ще знаю обшир функцій. Прогляну за вашим посиланням, дякую.

9

Re: Чому локальний string не знищується по закінченню функції

За порадою lucas-kane переробив функцію з функціями <string.h>. Вийшло набагато компактніше, юхуу.

char* latinize(char *word) {
    static char latinized_word[50];

    char buffer[50];
    strcpy(buffer, &word[1]);
    strncat(buffer, word, 1);
    strcat(buffer, "ay");

    strcpy(latinized_word, buffer);
    return latinized_word;
}
Подякували: lucas-kane1

10

Re: Чому локальний string не знищується по закінченню функції

wander написав:
lucas-kane написав:
wander написав:

Ні. Це не так, array != pointer.

Ім'я масиву є вказівником на ділянку пам'яті, так само як і перший елемент масиву, а точніше його адреса...
Частково це і є вказівник, конкретніше його окремий випадок. Тобто коли вказівнику присвоюється певний блок пам'яті (Для цього випадку у СТЕКУ)

Це ви собі так вигадали і видаєте бажане за дійсне? Бо з дійсністю це має вкрай мало спільного.
До ознайомлення - тиць

Так згоден написав маячню... Мав на увазі зовсім інше. Але виразивши свою думу і за підтвердженням хотів відкрити Brian Kernighan, Dennis Ritchie випадково натиснув відправити (сонний був, а як відредагувати чи видалити щойно надіслане повідомлення не знаю). Просто хотів зауважити для початківця, що Масиви і Вказівники настільки взаємопов'язані, що їх навіть розглядають одночасно.
Вибачаюсь. Я не являють по професії програмістом, - це скоріше хобі.

Подякували: bebyk1

11

Re: Чому локальний string не знищується по закінченню функції

lucas-kane написав:
wander написав:
lucas-kane написав:

Ім'я масиву є вказівником на ділянку пам'яті, так само як і перший елемент масиву, а точніше його адреса...
Частково це і є вказівник, конкретніше його окремий випадок. Тобто коли вказівнику присвоюється певний блок пам'яті (Для цього випадку у СТЕКУ)

Це ви собі так вигадали і видаєте бажане за дійсне? Бо з дійсністю це має вкрай мало спільного.
До ознайомлення - тиць

Так згоден написав маячню... Мав на увазі зовсім інше. Але виразивши свою думу і за підтвердженням хотів відкрити Brian Kernighan, Dennis Ritchie випадково натиснув відправити (сонний був, а як відредагувати чи видалити щойно надіслане повідомлення не знаю). Просто хотів зауважити для початківця, що Масиви і Вказівники настільки взаємопов'язані, що їх навіть розглядають одночасно.
Вибачаюсь. Я не являють по професії програмістом, - це скоріше хобі.

Та і я теж не мав на увазі, що array це і є pointer, лише те, що array є вказівником на нульовий індекс.

Прихований текст

Проте спілкуватися з паном wander'ом втішає мало, тому й не відповідав нічого, бо не хотів провокувати куп гівна в цій темі.

12

Re: Чому локальний string не знищується по закінченню функції

bebyk написав:

За порадою lucas-kane переробив функцію з функціями <string.h>. Вийшло набагато компактніше, юхуу.

char* latinize(char *word) {
    static char latinized_word[50];

    char buffer[50];
    strcpy(buffer, &word[1]);
    strncat(buffer, word, 1);
    strcat(buffer, "ay");

    strcpy(latinized_word, buffer);
    return latinized_word;
}

1. Вам не потрібна одна з двох змінних - buffer та latinized_word роблять абсолютно одне й те саме.
2. В принципі, "ручне" рішення може бути дещо ефективнішим - усі strcpy та strcat-и додають нулі в кінець. Хоча, звісно, це дуже маленькі втрати.
3. Якщо вхідне слово буде довшим за 47 символів, то буфер переповниться з невизначеною поведінкою.
4. Якщо десь буде два виклики цієї функції без копіювання, повернене значення перезапишеться, а цього зазвичай не очікують. Скажімо,

printf("pig latin will be %s %s", latinize("pig"), latinize("latin"));

виводить зовсім не те, що ви могли б очікувати. Щоб уникнути цього, передавайте буфер параметром, десь так:

char* latinize( /*IN*/ char *word, /*OUT*/ char *latinized) //buffer must be at least strlen(word)+3
{
    ...
    return latinized;
}

Тепер, принаймні, для того, хто використовуватиме цю функцію, буде очевидно, в чому проблема.

Подякували: wander, bebyk2

13

Re: Чому локальний string не знищується по закінченню функції

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

За порадою lucas-kane переробив функцію з функціями <string.h>. Вийшло набагато компактніше, юхуу.

char* latinize(char *word) {
    static char latinized_word[50];

    char buffer[50];
    strcpy(buffer, &word[1]);
    strncat(buffer, word, 1);
    strcat(buffer, "ay");

    strcpy(latinized_word, buffer);
    return latinized_word;
}

1. Вам не потрібна одна з двох змінних - buffer та latinized_word роблять абсолютно одне й те саме.
2. В принципі, "ручне" рішення може бути дещо ефективнішим - усі strcpy та strcat-и додають нулі в кінець. Хоча, звісно, це дуже маленькі втрати.
3. Якщо вхідне слово буде довшим за 47 символів, то буфер переповниться з невизначеною поведінкою.
4. Якщо десь буде два виклики цієї функції без копіювання, повернене значення перезапишеться, а цього зазвичай не очікують. Скажімо,

printf("pig latin will be %s %s", latinize("pig"), latinize("latin"));

виводить зовсім не те, що ви могли б очікувати. Щоб уникнути цього, передавайте буфер параметром, десь так:

char* latinize( /*IN*/ char *word, /*OUT*/ char *latinized) //buffer must be at least strlen(word)+3
{
    ...
    return latinized;
}

Тепер, принаймні, для того, хто використовуватиме цю функцію, буде очевидно, в чому проблема.

Дякую, цінні зауваги. Спробую ще 4, що ви порадили.

А щодо 3 — думав про це. Чи достатньо буде просто чекнути token і return 1 з попередженням, що вхідне слово не має перевищувати стільки-от символів? Чи це не дуже практика?

14

Re: Чому локальний string не знищується по закінченню функції

статичні змінні, ініціалізовані у функціях... це форум програмістів, чи клюб латентних факаперів?

15

Re: Чому локальний string не знищується по закінченню функції

koala,

Зробив варіант 4, як ви запропонували. Справді так виходить зрозуміліше, коли читати.

Тільки коли два рази викликати до printf, то все одно перезаписується. Це очікувано?

Якщо я, звісно правильно зрозумів, що ви мали на увазі.

char* latinize(char *word, char *latinized);

int main() {
    …
    char buffer[50];
    …
    printf("pig latin will be %s %s", latinize("pig", buffer), latinize("latin", buffer));

    return 0;
}

char* latinize(char *word, char *latinized) {
    strcpy(latinized, &word[1]);
    strncat(latinized, word, 1);
    strcat(latinized, "ay");

    return latinized;
}

16

Re: Чому локальний string не знищується по закінченню функції

)) Так. По перше. Не зважаючи на роботу коду у головній функції в printf, Ви до буферу копіюєте  слово. Коли Ви викличете latinize один раз, то вона спрацює і видасть правильний результат.
А от у Вашому випадку, виклик двічі приводить до перезапису Буферу, тобто перший раз виклику

buf = "atinlay\0"

, а другий -

buf = "ig\0nlay\0"

. Поверх попереднього результату запише інший...
По друге,

printf("%s %s", latinize("pig", buf), latinize("latin", buf));

- Виведе результат на екран тільки у тому випадку, коли в середині неї завершать роботу виклики інших функцій, а саме latinize("pig", buf), latinize("latin", buf). А оскільки Буфер в якому збереглись результати роботи програми один, то і результат Вашої роботи буде дублюватись, крім того, ще й неправильно...

Подякували: koala1

17

Re: Чому локальний string не знищується по закінченню функції

Щоб уникнути перезапису буферу потрібно не копіювати кожного разу слова до нього а додавати.

#include <stdio.h>
#include <string.h>

char* latinize(const char*, char*);

int main()
{
    char buf[50] = "";
    printf("%s\n", latinize("pig", buf));
    printf("%s\n", latinize("latin", buf));

    return 0;
}

char* latinize(const char* word, char* buf)
{
    //strcpy(buf, &word[1]);
    strcat(buf, &word[1]);
    strncat(buf, word, 1);
    strcat(buf, "ay ");

    return buf;
}

Ну а щоб вам уникнути Дублювання під час одночасного виклику функції latinize з одним буфером потрібно переробити код. Так не піде.

Подякували: bebyk1

18 Востаннє редагувалося bebyk (16.01.2021 19:10:36)

Re: Чому локальний string не знищується по закінченню функції

То значить очікувано. Оце цінна для мене інфа:

lucas-kane написав:

Виведе результат на екран тільки у тому випадку, коли в середині неї завершать роботу виклики інших функцій

В інтерпретованих мовах, з якими я працюю, то зазвичай геть по-іншому.

Тож цікаво, як переробити, щоб можна було викликати 2 функції в одному printf й отримати різну видачу на різні аргументи?

lucas-kane написав:

Ну а щоб вам уникнути Дублювання під час одночасного виклику функції latinize з одним буфером потрібно переробити код. Так не піде.

Також, так і не зрозумів, навіщо це, коли я викликаю по функції в одному printf:

lucas-kane написав:

Щоб уникнути перезапису буферу потрібно не копіювати кожного разу слова до нього а додавати.

Все коректно працює з перезаписом. Щоб уникнути додаткових \0? Чи я щось випустив з уваги? Чи ви це написали, як перше, що потрібно для згаданої переробки коду?

19

Re: Чому локальний string не знищується по закінченню функції

bebyk написав:

Тож цікаво, як переробити, щоб можна було викликати 2 функції в одному printf й отримати різну видачу на різні аргументи?

В одному printf із одним буфером ніяк. Це не можливо... Ось відповідь.... Друге питання скільки цих буферів Вам знадобиться Не знаю..
У даному випадку викликати можна тільки окремо і перед кожним викликом чистити буфер.
Можете спробувати так.

bebyk написав:

Також, так і не зрозумів, навіщо це, коли я викликаю по функції в одному printf:

Для того, щоб результати відрізняли... Я ж описав вище Вашу проблему. Так хоч трохи є відмінності у вихідних даних

bebyk написав:

Все коректно працює з перезаписом. Щоб уникнути додаткових \0? Чи я щось випустив з уваги? Чи ви це написали, як перше, що потрібно для згаданої переробки коду?

А результат на виході спів падає з умовою задачі?

Подякували: bebyk1

20 Востаннє редагувалося bebyk (16.01.2021 19:26:58)

Re: Чому локальний string не знищується по закінченню функції

lucas-kane написав:

А результат на виході спів падає з умовою задачі?

Так, коли по одній функції на printf, то й з копіюванням усе видає коректно. Тому й уточнюю.  :)

У всякому разі дякую за детальне пояснення, це мені не зайве.