Я работаю с библиотекой 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);
Чтобы избежать неопределенного поведения в форме возврата указателя на память, выделенную стеком.
Типы возвращаемых значений никогда не могут быть статическими.
static
— это спецификатор класса хранилища, который не является частью типа.
@wohlstad Итак, следует ли мне переписать его, чтобы использовать указатель возврата в качестве аргумента?
Поскольку reg_val
является самим собой static
, его нет в стеке, и, следовательно, AFAIK можно вернуть его адрес в виде указателя на него. Поэтому ваше последнее чувство («Чтобы избежать неопределенного поведения в форме возврата указателя на память, выделенную в стеке») не должно вызывать беспокойства.
static
в коде являются примером этого — они не делают одно и то же.
@BoP Ну, они оба меняют связь объекта или функции, к которой они применяются.
@IanAbbott - С другой стороны, для функции эффектом является видимость, а для данных эффектом является их время жизни. То же, то же самое, но другое.
@n0rmi, как и просили, я превратил свои комментарии в ответ, вы можете удалить последнее изменение своего вопроса.
@BoP Правда, связь (если таковая имеется) и время жизни объекта зависят от класса хранилища. Правда, переменная, объявленная в теле функции, все равно не имела никакой связи, поэтому ее объявление static
не влияло на ее связь, а только на ее время жизни.
Превратил мои комментарии в ответ, как и просили:
static int* foo() { /* ... */ }
(а также read_multiple...
или любая подобная функция) — это статическая функция, возвращающая указатель.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
).
Это также может быть полезно: Что находится в разных типах памяти микроконтроллера?
Это статическая функция, возвращающая указатель. Что такое «статический указатель» в этом контексте?