Почему вызов функции в другом порядке вызывает ошибку сегментации в C?

Я работаю над программой на C, которая обрабатывает массив указателей на символы. У меня есть несколько функций, которые работают с этим массивом, включая longest_name(), unique() и другие. Когда я вызываю функцию longest_name() после unique() в функции main(), программа работает нормально. Но когда я меняю порядок и вызываю longest_name() перед unique(), я получаю ошибку сегментации.

Я знаю, что указатель char *name в функции longest_name() должен выделяться malloc, чтобы избежать ошибки сегментации. Однако я хочу понять, почему программа работает в одном сценарии, а не в другом.

Я подозреваю, что проблема может быть связана с выделением памяти или манипулированием указателем, но я не уверен, с чего начать поиск. Может ли кто-нибудь объяснить, почему это происходит и как это исправить?

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


void display_name(char **stu, int indx);
void vowel_count(char **stu, int indx);
void longest_name(char **stu);
void unique(char **stu);



int main()
{
    char *name[10] = {"Aakash", "Anirudha", "Vikas", "Vinay", "Rakesh", "Thomas", "Jerry", "Alekha", "Daksh", "Peter"};
    display_name(name, 4);
    vowel_count(name,9);
    
    //longest_name(name);       //not run
    
    unique(name);
    
    longest_name(name);         //run

    return 0;
}

void unique(char **stu){
    int len = 0, found = 0;
    printf("Name whose first and last character is \'a\' :- ");
    for(int i = 0; i < 10; i++){
        len = strlen(stu[i]);
        if (stu[i][0] == 'a' && stu[i][0] == 'a'){
            found = 1;
            printf("%s\n", stu[i]);
        }
    }
    if (found == 0){
        printf("None\n");
    }
}

void longest_name(char **stu){
    int len = 0, mx = 0;
    char *name;
    printf("\n Address is %p \n",name);
    for(int i = 0; i < 10; i++){
        if (mx < strlen(stu[i])){
            mx = strlen(stu[i]);
            strcpy(name, stu[i]);
        }
    }
    printf("Longest name is \"%s\" with length %d\n", name, mx);
}

void vowel_count(char **stu, int indx){
    
    indx--;
    for(int i = 0; i < 10; i++){
        if (i == indx){
            int len = strlen(stu[i]), cnt = 0;
            char name[len];
            strcpy(name, stu[i]);
            for(int j = 0; j < len; j++){
                char c = tolower(name[j]);
                if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
                   cnt++;
            }
            printf("Number of vowels in \"%s\" are :- %d\n", name, cnt);
            break;
        }
    }
    
}

void display_name(char **stu, int indx){
    indx--;
    for(int i = 0; i < 10; i++){
        if (i == indx)
            printf("%s\n",stu[i]);
    }
}




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

Указатель char* name в longest_name не инициализирован, он никуда не указывает. Вы получаете «неопределенное поведение» (погуглите этот термин).

Jabberwocky 17.02.2023 10:39

В функции longest_name переменная name является небинилизированной переменной-указателем. Вы не можете использовать его, не указав на что-то действительное.

Some programmer dude 17.02.2023 10:39

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

Some programmer dude 17.02.2023 10:41

Я знаю, что он должен быть инициализирован, но мой вопрос в том, почему один и тот же код работает в одном сценарии, но не в другом?

Aman Pachouri 17.02.2023 10:43

Я хочу знать, почему один и тот же указатель работает, когда я вызываю после unique(), а не перед unique()?

Aman Pachouri 17.02.2023 10:44

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

Weather Vane 17.02.2023 10:48

Потому что неопределенное поведение. Иногда может показаться, что он работает, иногда он может давать странные результаты, иногда он может привести к сбою вашей программы, а иногда может поджечь вашу кошку. Ну может не последний, но вообще непредсказуемый. Многие думают, что когда кажется, что это работает, это на самом деле из-за невезения. Используйте отладчики памяти, такие как Valgrind, и дезинфицирующие средства компилятора, чтобы обнаружить подобные проблемы.

Some programmer dude 17.02.2023 10:51

Я не пытаюсь заставить программу работать, и я уже знаю, где исправить проблему, чтобы она работала нормально. Вместо этого мне интересно, почему возникает эта проблема. В частности, я хочу знать, почему программа работает без проблем, когда я вызываю longest_name() после unique() в функции main(), но получаю ошибку сегментации, когда переключаю порядок и вызываю unique() после longest_name(). Код одинаков в обоих сценариях, но проблема возникает из-за изменения порядка вызовов функций.

Aman Pachouri 17.02.2023 10:54

@AmanPachouri проблема возникает из-за того, что указатель не был инициализирован. Он содержит неопределенное значение. В зависимости от того, что ваша программа делала раньше, может случиться так, что это неопределенное значение случайно указывает на допустимую ячейку памяти, и segfault не произойдет. Исход не определен. Например, если вы проедете на красный свет, иногда вас сбивает грузовик, иногда вас сбивает велосипед, а иногда вы справляетесь с этим. Вы не знаете, что произойдет.

Jabberwocky 17.02.2023 10:56

Примечание: внимательно следите за const правильностью! Все функции, не изменяющие C-строки, должны принимать указатели на const, например. void unique(char const** stu); - особенно: хотя строковые литералы («литерал!») на самом деле имеют тип char[] (наследие, полученное в результате времен, когда const еще не существовало), они по-прежнему неизменяемы. Таким образом, вы никогда не должны назначать их чему-либо, кроме указателей const, иначе однажды вы столкнетесь с попыткой изменить их (неопределенное поведение!), поэтому в вашем случае: char* name[] = { ... };

Aconcagua 17.02.2023 11:16

Если программа работает успешно по счастливой случайности в одном случае, то почему она так постоянна, как при каждой попытке, когда она работает успешно, она должна когда-то терпеть неудачу, и почему она постоянно терпит неудачу в другом случае при каждой попытке.

Aman Pachouri 17.02.2023 11:17

Боковое примечание: если в любом случае имеется ровно десять имен, я бы предпочел вывести размер массива из инициализатора — см. размер, который был опущен в моем предыдущем комментарии.

Aconcagua 17.02.2023 11:18

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

Weather Vane 17.02.2023 11:22

@Aconcagua Это «не должно» ничего делать. Он свободен делать все, что хочет.

klutt 17.02.2023 11:23

Немного более подробно о том, почему программа случайно сработала в одном случае (общее объяснение): параметры функции и локальные переменные (которые на самом деле не что иное, как параметры) обычно хранятся в стеке (иногда в регистрах) — вызов функции резервирует и заполняет часть этого стека, возвращаясь из, снова освобождает его, оставляя содержимое неизменным. При вызове другой функции неинициализированная переменная может (случайно) оказаться точно в том же месте, где была правильно инициализированная переменная, сохраненная в предыдущем вызове функции, и новый вызов затем использует это значение.

Aconcagua 17.02.2023 11:24

Ошибка в моем предыдущем комментарии (извините!): Должна, конечно, быть: char const* name[] = { ... };

Aconcagua 17.02.2023 11:25

@klutt Что именно вы имеете в виду под «должно» — или вы подразумеваете под «этим» автора вопроса? В последнем случае: Действительно, на самом деле бесплатно — мой комментарий просто совет, но сильный. Я бы приложил руку к огню, потому что, если не следовать ему, однажды «он» столкнется с UB для изменения литералов;)

Aconcagua 17.02.2023 11:27

@Aconcagua Я имею в виду программу. У ОП есть ожидания относительно того, как должен проявить себя UB.

klutt 17.02.2023 11:30

@klutt Я думаю, вы намеревались сослаться на кого-то другого, мои единственные комментарии до этого упоминали, что указатели должны указывать на const, кроме тех, которые предназначены для модификации ...

Aconcagua 17.02.2023 11:35

@Aconcagua Я вижу, что я сделал неправильно. Я набрал @, затем a, затем нажал клавишу ввода :)

klutt 17.02.2023 11:37

Aconcagua 17.02.2023 11:45

@klutt Неважно;)

Aconcagua 17.02.2023 11:46

@AmanPachouri 'как это исправить?' — вы на самом деле уже сами дали ответ: пусть name указывает на допустимое место в памяти — malloc память — это один из способов, но, поскольку вы все равно не перераспределяете, локально определенный массив (char name[128]) может быть предпочтительнее.

Aconcagua 17.02.2023 11:58

Aman Pachouri 17.02.2023 11:58
Конечные и Readonly классы в PHP
Конечные и Readonly классы в PHP
В прошлом, когда вы не хотели, чтобы другие классы расширяли определенный класс, вы могли пометить его как final.
От React к React Native: Руководство для начинающих по разработке мобильных приложений с использованием React
От React к React Native: Руководство для начинающих по разработке мобильных приложений с использованием React
Если вы уже умеете работать с React, создание мобильных приложений для iOS и Android - это новое приключение, в котором вы сможете применить свои...
БЭМ: Конвенция об именовании CSS
БЭМ: Конвенция об именовании CSS
Я часто вижу беспорядочный код CSS, особенно если проект большой. Кроме того, я совершал эту ошибку в профессиональных или личных проектах и...
Революционная веб-разработка ServiceNow
Революционная веб-разработка ServiceNow
В быстро развивающемся мире веб-разработки ServiceNow для достижения успеха крайне важно оставаться на вершине последних тенденций и технологий. По...
Как добавить SEO(Search Engine Optimization) в наше веб-приложение и как это работает?
Как добавить SEO(Search Engine Optimization) в наше веб-приложение и как это работает?
Заголовок веб-страницы играет наиболее важную роль в SEO, он помогает поисковой системе понять, о чем ваш сайт.
Конфигурация Jest в angular
Конфигурация Jest в angular
В этой статье я рассказываю обо всех необходимых шагах, которые нужно выполнить при настройке jest в angular.
1
24
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как упоминалось в комментариях, ваша переменная name в вашей longest_name функции не инициализирована.

Вы объявили это так:

char *name;

И нигде об этом не указывалось.

Многие компиляторы вручную инициализируют указатель нулем, например, написав:

char *name = NULL;

... но это не гарантируется, поэтому рекомендуется всегда инициализировать ваши указатели на NULL, если вы пока не хотите, чтобы они указывали куда-либо. Тем не менее, важно отметить, что инициализация NULL просто означает, что указатель определенно никуда не указывает.

В вашем случае похоже, что ваш указатель name мог быть инициализирован некоторым случайным значением, например, независимо от того, что ранее находилось в этом месте в стеке. В последнем случае это объясняет, почему ваша программа иногда работает, а иногда нет. Как упоминалось в комментарии, порядок, в котором вы вызываете функции, будет определять, что находится в стеке (под указателем стека) к моменту вызова второй функции. Таким образом, вполне вероятно, что когда вы сначала вызываете свою функцию unique, а ЗАТЕМ функцию longest_name, по счастливой случайности ваша переменная name в вашей функции longest_name инициализируется некоторой «действительной» ячейкой памяти, что означает, что вы можете записывать в нее данные. .

То, что происходит, описывается как неопределенное поведение. По сути, это означает, что ваша программа может иногда работать так, как вы ожидали, а иногда делать что-то совершенно другое. Неопределенное поведение возникает, когда что-то в программе написано неправильно, но это не обязательно всегда приводит к мгновенному сбою программы. Однако, если вы правильно напишете программу, вы сможете полностью избежать UB (неопределенного поведения).

Таким образом, вы никогда не должны делать что-то вроде:

char *whatever = "It was a sunny day.";
char *str;
strcpy(str, whatever);

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

В вашем случае ваша функция longest_name должна выделять память и указывать указатель name на эту выделенную память, прежде чем что-либо копировать в нее, например:

name = malloc((strlen(stu[i]) + 1)*sizeof(char));
strcpy(name, stu[i]);

... не забывая free память после ее использования.

Помните, что строка, хранящаяся как char*, всегда должна включать дополнительный байт для нулевого символа завершения '\0' или просто 0 в ASCII. Здесь вы ошиблись в своей функции vowel_count, ваше объявление имени должно быть:

char name[len + 1];

Также обратите внимание, что, объявляя name таким образом, вы объявляете массив переменной длины (VLA), что может быть непросто. Если вам нужна память с динамическим размером (определяемым во время выполнения), обычно лучше использовать динамическое выделение памяти (используя malloc и free для освобождения).

Кроме того, в вашей функции longest_name вам не нужно выделять дополнительную память, все, что вам нужно, это сделать так, чтобы указатель name указывал на самую длинную строку и распечатывал ее, например:

void longest_name(char **stu){
    size_t len = 0, mx = 0; // strlen returns a number of type size_t
    char *name = NULL; // initialise to NULL since not pointing anywhere
    printf("\n Address is %p\n", name); // this will either be zero or undefined
    for(unsigned int i = 0; i < 10; i++){ // use unsigned as you start from zero
        if (mx < (len = strlen(stu[i]))){ // assign len to only call strlen once
            mx = len;
            name = stu[i]; // simply make name point to the longest string
        }
    }
    printf("Longest name is \"%s\" with length %zu\n", name, mx);
}

В заключение, ваша программа иногда запускается, а иногда падает, потому что ваша переменная name иногда указывает на что-то «действительное», а иногда нет. Вы можете исправить это, всегда проверяя, что ваши указатели указывают на действительное место перед их использованием.

Вместо использования malloc и strcpy вы можете использовать strdup — для этого он и был создан.

Mark Ransom 17.02.2023 17:19

Действительно, но я также хотел подчеркнуть, что в этом случае даже не было необходимости в динамической памяти! Отличный момент, однако

Gregor Hartl Watters 17.02.2023 17:20

Да, извините - не дочитал до конца ответ. Однако вы все равно должны были инициализировать name либо в NULL, либо в stu[0].

Mark Ransom 17.02.2023 17:25

Упс, я и тут сам себе противоречу! Сейчас отредактирую :) спасибо

Gregor Hartl Watters 17.02.2023 17:26

Другие вопросы по теме