Я работаю над программой на 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]);
}
}
Я попытался запустить программу на другой машине, так как думал, что проблема может быть связана с компилятором. Однако поведение было таким же и на другой машине.
В функции longest_name переменная name является небинилизированной переменной-указателем. Вы не можете использовать его, не указав на что-то действительное.
На самом деле, сохраните индекс самого длинного имени вместо копирования строки. Затем используйте его для печати строки из массива.
Я знаю, что он должен быть инициализирован, но мой вопрос в том, почему один и тот же код работает в одном сценарии, но не в другом?
Я хочу знать, почему один и тот же указатель работает, когда я вызываю после unique(), а не перед unique()?
Потому что так случилось. В одном случае указатель оказался случайно действительным, в другом — нет. Это как спрашивать, почему тебя не раздавило, когда ты проехал на красный свет. Потому что ты этого не сделал. Вы никогда не будете раздавлены, если остановитесь на светофоре, но если вы этого не сделаете, результат будет неопределенным.
Потому что неопределенное поведение. Иногда может показаться, что он работает, иногда он может давать странные результаты, иногда он может привести к сбою вашей программы, а иногда может поджечь вашу кошку. Ну может не последний, но вообще непредсказуемый. Многие думают, что когда кажется, что это работает, это на самом деле из-за невезения. Используйте отладчики памяти, такие как Valgrind, и дезинфицирующие средства компилятора, чтобы обнаружить подобные проблемы.
Я не пытаюсь заставить программу работать, и я уже знаю, где исправить проблему, чтобы она работала нормально. Вместо этого мне интересно, почему возникает эта проблема. В частности, я хочу знать, почему программа работает без проблем, когда я вызываю longest_name() после unique() в функции main(), но получаю ошибку сегментации, когда переключаю порядок и вызываю unique() после longest_name(). Код одинаков в обоих сценариях, но проблема возникает из-за изменения порядка вызовов функций.
@AmanPachouri проблема возникает из-за того, что указатель не был инициализирован. Он содержит неопределенное значение. В зависимости от того, что ваша программа делала раньше, может случиться так, что это неопределенное значение случайно указывает на допустимую ячейку памяти, и segfault не произойдет. Исход не определен. Например, если вы проедете на красный свет, иногда вас сбивает грузовик, иногда вас сбивает велосипед, а иногда вы справляетесь с этим. Вы не знаете, что произойдет.
Примечание: внимательно следите за const правильностью! Все функции, не изменяющие C-строки, должны принимать указатели на const, например. void unique(char const** stu); - особенно: хотя строковые литералы («литерал!») на самом деле имеют тип char[] (наследие, полученное в результате времен, когда const еще не существовало), они по-прежнему неизменяемы. Таким образом, вы никогда не должны назначать их чему-либо, кроме указателей const, иначе однажды вы столкнетесь с попыткой изменить их (неопределенное поведение!), поэтому в вашем случае: char* name[] = { ... };
Если программа работает успешно по счастливой случайности в одном случае, то почему она так постоянна, как при каждой попытке, когда она работает успешно, она должна когда-то терпеть неудачу, и почему она постоянно терпит неудачу в другом случае при каждой попытке.
Боковое примечание: если в любом случае имеется ровно десять имен, я бы предпочел вывести размер массива из инициализатора — см. размер, который был опущен в моем предыдущем комментарии.
Непротиворечивость заключается в том, что значение, оставшееся в неинициализированной переменной (от предыдущего использования этой памяти), непротиворечиво в каждом случае. Неопределенное поведение не означает случайное.
@Aconcagua Это «не должно» ничего делать. Он свободен делать все, что хочет.
Немного более подробно о том, почему программа случайно сработала в одном случае (общее объяснение): параметры функции и локальные переменные (которые на самом деле не что иное, как параметры) обычно хранятся в стеке (иногда в регистрах) — вызов функции резервирует и заполняет часть этого стека, возвращаясь из, снова освобождает его, оставляя содержимое неизменным. При вызове другой функции неинициализированная переменная может (случайно) оказаться точно в том же месте, где была правильно инициализированная переменная, сохраненная в предыдущем вызове функции, и новый вызов затем использует это значение.
Ошибка в моем предыдущем комментарии (извините!): Должна, конечно, быть: char const* name[] = { ... };
@klutt Что именно вы имеете в виду под «должно» — или вы подразумеваете под «этим» автора вопроса? В последнем случае: Действительно, на самом деле бесплатно — мой комментарий просто совет, но сильный. Я бы приложил руку к огню, потому что, если не следовать ему, однажды «он» столкнется с UB для изменения литералов;)
@Aconcagua Я имею в виду программу. У ОП есть ожидания относительно того, как должен проявить себя UB.
@klutt Я думаю, вы намеревались сослаться на кого-то другого, мои единственные комментарии до этого упоминали, что указатели должны указывать на const, кроме тех, которые предназначены для модификации ...
@Aconcagua Я вижу, что я сделал неправильно. Я набрал @, затем a, затем нажал клавишу ввода :)
@klutt Неважно;)
@AmanPachouri 'как это исправить?' — вы на самом деле уже сами дали ответ: пусть name указывает на допустимое место в памяти — malloc память — это один из способов, но, поскольку вы все равно не перераспределяете, локально определенный массив (char name[128]) может быть предпочтительнее.
Как упоминалось в комментариях, ваша переменная 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 — для этого он и был создан.
Действительно, но я также хотел подчеркнуть, что в этом случае даже не было необходимости в динамической памяти! Отличный момент, однако
Да, извините - не дочитал до конца ответ. Однако вы все равно должны были инициализировать name либо в NULL, либо в stu[0].
Упс, я и тут сам себе противоречу! Сейчас отредактирую :) спасибо
Указатель char* name в longest_name не инициализирован, он никуда не указывает. Вы получаете «неопределенное поведение» (погуглите этот термин).