C: почему я получаю ошибку сегментации?

У меня есть следующий код. Вызов f1 вызывает segfault, но не f2.

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

void f1(unsigned char** arr)
{
   unsigned char* p = *arr;
   *p = 'h';
   p++;
   *p = '\0';
}

void f2(unsigned char* arr)
{
   unsigned char* p = arr;
   *p = 'h';
   p++;
   *p = '\0';
}

int main()
{
   unsigned char a[31];
   f1((unsigned char**)&a);
   //f2(a);
   printf("%s\n", a);
}

Проблема в том, что приведение (unsigned char**)&a. Итак, первая функция пытается использовать неопределенное значение выражения указателя *arr.

Vlad from Moscow 08.08.2024 19:38
Ранее мы видели, что тип &a не является unsigned char**. Настаивать на том, что это так, еще не значит, что это так.
BoP 08.08.2024 20:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
2
106
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

unsigned char** — это указатель на указатель на unsigned char.

&a возвращает адрес массива a[]. Тип, который возвращает &a, — это unsigned char (*)[31], а не unsigned char**, поэтому вы не можете просто вызвать f1(a), что является несоответствием типов. Обратите внимание на эту ошибку, это важно.

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

IOW, вы берете указатель на массив и преобразуете его в указатель на указатель, а это не так. Внутри f1() ожидается, что разыменование *arr даст указатель unsigned char* на действительный unsigned char, но на самом деле это не так. arr указывает на сам a[], и, таким образом, чтение *arr будет читать и неправильно интерпретировать первые 4 байта (в 32-битной сборке) или 8 байтов (в 64-битной сборке) a[] как значение указателя, а это не так. Тогда разыменование этого указателя не удается.

Не используйте слепо приведение типов только для того, чтобы заглушить ошибки компилятора. Это ошибки по какой-то причине. Приведение типов полезно в определенных случаях, но это не один из них. Правильный способ вызова f1() с помощью a[] в вашем примере выглядит следующим образом:

unsigned char a[31];
unsigned char *ptr = a; // aka: = &a[0]
f1(&ptr);

Альтернативно, измените f1(), чтобы принять указатель на массив вместо указателя на указатель:

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

void f1(unsigned char (*arr)[31])
{
   unsigned char* p = *arr;
   *p = 'h';
   p++;
   *p = '\0';
}

int main()
{
   unsigned char a[31];
   f1(&a);
   printf("%s\n", a);
}

Что касается того, почему вызов f2(a) работает как есть, то это потому, что массив распадается на указатель на свой 1-й элемент, и f2() правильно использует этот указатель. Это не тот случай, когда вы звоните f1(&a).

a — адрес массива, &a — адрес a, в f1, *arr указывает на массив, я что-нибудь пропустил?

user180574 08.08.2024 19:49

@user180574 user180574 да, это так. a — это не адрес массива, а сам массив. &a — адрес массива. Я обновил свой ответ.

Remy Lebeau 08.08.2024 19:51

Я думал, что указатель и массив в C — это одно и то же, нет?

user180574 08.08.2024 19:54

@RemyLebeau, не могли бы вы опубликовать код, который будет работать?

JosephDoggie 08.08.2024 20:02

@JosephDoggie, я уже сделал это

Remy Lebeau 08.08.2024 20:05

@user180574 user180574 «Я думал, что указатель и массив в C — это одно и то же» - нет, и на самом деле вам уже сказали именно это в вашем предыдущем вопросе.

Remy Lebeau 08.08.2024 20:07

@RemyLebeau Отлично! Отличная работа....

JosephDoggie 08.08.2024 21:09

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