Является ли «static int* foo()» в C статической функцией, возвращающей указатель, или функцией, возвращающей статический указатель?

Я работаю с библиотекой Arduino, которую я слегка модифицировал для AVR C, которая взаимодействует с ICM-20948 IMU (интегрированным измерительным блоком). В нем я нашел вот эту «жемчужину»:

uint8_t* temp = read_multiple_icm20948_reg(ub_0, B0_TEMP_OUT_H, 2);

Где read_multiple... определяется как:

static uint8_t* read_multiple...(userbank ub, uint8_t reg, uint8_t len){
    static uint8_t reg_val[6];
    .
    .
    .
    return reg_val;
}

Мой вопрос: это статическая функция, возвращающая указатель, или обычная функция, возвращающая статический указатель? И должен ли я реструктурировать его, чтобы он выглядел так:

uint8_t* temp;
read_multiple_icm20948_reg(temp, ub_0, B0_TEMP_OUT_H, 2);

Где read_multiple... будет определяться как

void read_multiple...(uint8_t *ret, userbank ub, uint8_t reg, uint8_t len);

Чтобы избежать неопределенного поведения в форме возврата указателя на память, выделенную стеком.

Это статическая функция, возвращающая указатель. Что такое «статический указатель» в этом контексте?

wohlstad 21.05.2024 10:26

Типы возвращаемых значений никогда не могут быть статическими.

Some programmer dude 21.05.2024 10:27
static — это спецификатор класса хранилища, который не является частью типа.
Ian Abbott 21.05.2024 10:34

@wohlstad Итак, следует ли мне переписать его, чтобы использовать указатель возврата в качестве аргумента?

n0rmi 21.05.2024 10:37

Поскольку reg_val является самим собой static, его нет в стеке, и, следовательно, AFAIK можно вернуть его адрес в виде указателя на него. Поэтому ваше последнее чувство («Чтобы избежать неопределенного поведения в форме возврата указателя на память, выделенную в стеке») не должно вызывать беспокойства.

wohlstad 21.05.2024 10:40
static имеет несколько совершенно разных значений. Два static в коде являются примером этого — они не делают одно и то же.
BoP 21.05.2024 10:41

@BoP Ну, они оба меняют связь объекта или функции, к которой они применяются.

Ian Abbott 21.05.2024 10:49

@IanAbbott - С другой стороны, для функции эффектом является видимость, а для данных эффектом является их время жизни. То же, то же самое, но другое.

BoP 21.05.2024 10:51

@n0rmi, как и просили, я превратил свои комментарии в ответ, вы можете удалить последнее изменение своего вопроса.

wohlstad 21.05.2024 11:03

@BoP Правда, связь (если таковая имеется) и время жизни объекта зависят от класса хранилища. Правда, переменная, объявленная в теле функции, все равно не имела никакой связи, поэтому ее объявление static не влияло на ее связь, а только на ее время жизни.

Ian Abbott 21.05.2024 19:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
10
118
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Превратил мои комментарии в ответ, как и просили:

  1. Функция static int* foo() { /* ... */ } (а также read_multiple... или любая подобная функция) — это статическая функция, возвращающая указатель.
    В этом контексте не существует такого понятия, как «статический указатель».
  2. Можно вернуть reg_val в функцию read_multiple....
    Это потому, что reg_val есть static, а это значит, что его нет в стеке. Поэтому можно вернуть его адрес в виде указателя на него.
    Ваше последнее предложение: Поэтому «во избежание неопределенного поведения в форме возврата указателя на выделенную в стеке память» не должно вызывать беспокойства.

Вы не можете вернуть спецификаторы класса хранения, такие как static, из функции — спецификаторы класса хранения находятся только в строке, где объявлен объект/функция. Спецификатор класса хранения принадлежит конкретному объекту/функции, а не типу объекта/функции (в отличие от квалификаторов типа const/volatile, которые принадлежат типу).

Анонимные данные, возвращаемые функцией, всегда имеют автоматический срок хранения, и изменить его невозможно — вы можете изменить тип, но не продолжительность хранения.

Таким образом, static в данном случае принадлежит объявлению/определению функции, а это означает, что функция имеет внутреннюю связь и может быть вызвана только из единицы перевода, где она объявлена/определена. В противном случае функции по умолчанию используют внешнюю связь (даже если вы не указываете ее с помощью extern), что означает, что их можно вызывать из разных единиц перевода.

То, что данные, на которые указывает возвращаемый указатель, тоже оказались static, это просто совпадение.

Дополнительная информация здесь: Что делает ключевое слово static в C?


И стоит ли мне его реструктуризировать?

Статическая продолжительность хранения частных функций — это подходящий и правильный способ разработки кода C. Таким образом, вы, возможно, действительно нашли драгоценный камень, но не таким саркастическим способом, как вы предполагали: использование static для частных функций — это просто то, как обычно и правильно пишутся программы на C, даже если вы можете не знать об этом.

Объекты и функции должны иметь как можно более узкую область применения, а внешнюю связь следует использовать только для функций, которые являются частью «API» библиотеки — интерфейса для программиста, использующего указанную библиотеку.

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

Однако static uint8_t reg_val[6]; не выделяется в стеке, поскольку переменные со статической продолжительностью хранения обычно размещаются либо в сегментах .data, либо в .bss ОЗУ. Здесь нет неопределенного поведения.

В частности, объект анонимного указателя uint8_t*, возвращаемый функцией, сам по себе имеет автоматическую продолжительность хранения (распределение стека или регистра), но указывает на объект со статической продолжительностью хранения (.data/.bss).

Это также может быть полезно: Что находится в разных типах памяти микроконтроллера?

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