У меня есть функция без параметров в C. Clang-16 предлагает мне избегать объявления функции без прототипа, но pthread_create
больше не работает с прототипом, у которого нет параметров. Это сценарий:
Файл main.c
пытается создать pthread из функции thread_function
:
// main.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "./thread.h"
int main(void) {
pthread_t thread_id;
pthread_create(&thread_id, NULL, thread_function, NULL);
pthread_join(thread_id, NULL);
return EXIT_SUCCESS;
}
Функция thread_function
объявлена в заголовочном файле thread.h
:
// thread.h
#ifndef _THREAD_H_
#define _THREAD_H_
void *thread_function();
#endif // _THREAD_H_
И определяется в файле thread.c
:
// thread.c
#include <stdio.h>
void *thread_function() {
printf("Hi\n");
return NULL;
}
Я скомпилировал программу с помощью clang-16
:
$ clang-16 main.c thread.c -o thread -Wpedantic
In file included from main.c:4:
././thread.h:4:22: warning: a function declaration without a prototype is deprecated in all versions of C [-Wstrict-prototypes]
void *thread_function();
^
void
1 warning generated.
thread.c:3:22: warning: a function declaration without a prototype is deprecated in all versions of C [-Wstrict-prototypes]
void *thread_function() {
^
void
1 warning generated.
Чтобы исправить предупреждения, я применил оба предложенных исправления:
// thread.h
#ifndef _THREAD_H_
#define _THREAD_H_
void *thread_function(void);
#endif // _THREAD_H_
А также в thread.c
:
// thread.c
#include <stdio.h>
void *thread_function(void) {
printf("Hi\n");
return NULL;
}
Теперь я больше не могу скомпилировать программу:
$ clang-16 main.c thread.c -o thread -Wpedantic
main.c:8:36: error: incompatible function pointer types passing 'void *(void)' to parameter of type 'void * _Nullable (* _Nonnull)(void * _Nullable)' [-Wincompatible-function-pointer-types]
pthread_create(&thread_id, NULL, thread_function, NULL);
^~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include/pthread.h:334:31: note: passing argument to parameter here
void * _Nullable (* _Nonnull)(void * _Nullable),
^
1 error generated.
Я изменил подпись функции на:
// thread.h
#ifndef _THREAD_H_
#define _THREAD_H_
void *thread_function(void *);
#endif // _THREAD_H_
А также:
// thread.c
#include <stdio.h>
void *thread_function(void *) {
printf("Hi\n");
return NULL;
}
Затем он компилируется, но больше не ANSI-совместим:
$ clang-16 main.c thread.c -o thread -Wpedantic
thread.c:3:29: warning: omitting the parameter name in a function definition is a C2x extension [-Wc2x-extensions]
void *thread_function(void *) {
^
1 warning generated.
Если я изменю подпись на void *thread_function(void *args)
, это больше не будет «функция без параметров». Как я могу создать pthread из функции без параметров и при этом быть ANSI-совместимым?
Что значит «ANSI-совместимый»? На самом деле для потоков не существует стандарта ANSI. Под «ANSI» вы имеете в виду C89? Или ПОСИКС?
@Gerhardh Требование заключалось в том, чтобы предоставление функции с фиктивным параметром вызывало предупреждение о неиспользуемой переменной.
Это не требование, а простой факт (в зависимости от компилятора). Вы имеете в виду, что требуется избегать предупреждения о «неиспользуемой переменной»? Эту проблему можно легко решить, не нарушая договоров. Описание API (включая правильную сигнатуру функции) — это договор, которому вы должны следовать.
@Лундин Ты прав! На самом деле мое предположение об API POSIX для потоков было ошибочным. Я не проверял точный аргумент на страницах руководства. Моей целью было написать в точном соответствии со стандартом POSIX и обеспечить обратную совместимость со старыми версиями C.
@Gerhardh Да, я хотел избежать использования ненужных переменных, но я не знал, что определение POSIX для pthread_create требует аргумента для функции.
Если вы хотите обеспечить совместимость ANSI с clang, вам нужно передать либо -std=c89
, либо -ansi
.
Вы можете явно отбросить аргумент, «приведя его к void
». Поскольку вам абсолютно необходимо назвать аргумент, чтобы удовлетворить -Wpedantic
, чтобы это действительно было действительным C (это совершенно другая история, когда компиляторы считают пропуск имени параметра приемлемым, если, например, -Wpedantic -Werror
. Спасибо Джону Боллинджеру за указание на это), вам придется использовать этот.
void *thread_function(void *arg) {
(void) arg;
printf("Hello");
return NULL;
}
Это компилируется с -ansi -Wall -Wpedantic -Wextra -Werror
. Обозреватель компилятора ссылка.
Опять же, предупреждение относительно безымянных параметров не имеет никакого отношения конкретно к «ANSI C». Итак, используйте -ansi
или -std=c89
тогда и только тогда, когда вы знаете, что делаете, и действительно требуете соответствия C89/ANSI. В противном случае вам, вероятно, просто нужен последний стандарт ISO, поддерживаемый вашим компилятором (см. man clang
и найдите -std
), или тот, который используется вашей командой и т. д.
В определении функции вам необходимо назвать все параметры, чтобы удовлетворить ограничениям языка C. Если вы опустите имена параметров в определении функции, результат будет недопустимым C, даже если вы сможете найти компилятор, который его принимает.
Ах, ладно, я этого не знал. Я сам никогда не опускаю имена параметров, поэтому никогда не беспокоился по этому поводу слишком сильно. Я отредактирую свой ответ.
Pthreads всегда ожидал указатель на функцию типа void* func (void*)
независимо от того, какой компилятор или стандарт C вы используете. Если вы когда-либо делали что-то еще, вы делали это неправильно.
void *thread_function();
— недопустимая функция обратного вызова потока во всех версиях C и POSIX. Аналогично, void *thread_function(void)
ничего не улучшает, поскольку это по-прежнему неправильный тип функции.
Последнее сообщение компилятора, которое вы получаете, связано с тем, что определение функции (в отличие от объявления/прототипа) без явных имен параметров является чепухой. Чтобы быть совместимым с ISO C (забудьте о «ANSI»), вам нужно дать ему имя:
void *thread_function (void* param)
Теперь, если вы не передаете функции какие-либо параметры, вы можете просто привести ее к void, чтобы отключить предупреждения: (void)param;
. В качестве альтернативы в предстоящем C23 параметр можно будет объявить как [[maybe_unused]] void* param
.
Если я изменю подпись на void *thread_function(void *args), это больше не будет «функция без параметров».
Этого никогда не было. Pthreads требуют функцию с параметром void*
, точка.
Программист не может решить, какие типы функций здесь использовать. Правильные типы для использования — это те, которые перечислены в API библиотеки: man pthread_create
:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Как вы прекрасно знаете, void *thread_function();
не является неправильным в C2011 и более ранних версиях. Он объявляет функцию, принимающую неопределенное количество аргументов. Программа, использующая такую функцию, как аргумент start_routine для pthread_create, является корректной. Если хотите, называйте это плохим стилем, но не вводите людей в заблуждение относительно того, что является действительным, а что нет.
@zwol Как сказал clang в вопросе ОП: «объявление функции без прототипа не рекомендуется во всех версиях C». Да, C позволяет вам писать всевозможные мусорные программы с несуществующей типобезопасностью, если вы настаиваете... действительно, вы можете присвоить void* (*) ()
void* (*)(void*)
до C23, но это не значит, что вам следует это делать.
Я никогда не говорил, что кто-то должен делать то, что делает код ОП. Я только хочу, чтобы вы пересмотрели свой ответ, чтобы признать различие между «код ОП - плохой стиль» и «код ОП неверен». В C2011 и более ранних версиях первое верно, а второе — нет.
Как я могу создать pthread из функции без параметров и при этом быть ANSI-совместимым?
На самом деле это не так уж и сложно, просто вызовите свою функцию с помощью функции перехода.
void *thread_function() {
printf("Hi\n");
return NULL;
}
void *thread_function_jump(void *arg) {
(void)arg; // To silence some compilers unused arguments warnings.
printf("Hi\n");
return thread_function();
}
int main(void) {
pthread_t thread_id;
pthread_create(&thread_id, NULL, thread_function_jump, NULL);
pthread_join(thread_id, NULL);
return EXIT_SUCCESS;
}
Кроме того:
#define _THREAD_H_
Не «совместим». Идентификаторы, начинающиеся с _
, за которым следует заглавная буква, зарезервированы, и нам не следует их использовать. Чтобы обеспечить совместимость, вам следует сменить защиту заголовка.
Это по-прежнему выдает предупреждение о неиспользуемом параметре, о котором и был вопрос.
Извините, вопрос в том, что это такое. Предупреждения не делают код ANSI несовместимым.
это больше не «функция без параметров». Правильный. Есть ли у вас особые требования по предоставлению функции, которая не соответствует требуемому типу?