Странное поведение getpwnam
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("%s %s\n", getpwnam("steve")->pw_name, getpwnam("root")->pw_name);
printf("%d %d\n", getpwnam("steve")->pw_uid, getpwnam("root")->pw_uid);
return EXIT_SUCCESS;
}
$ gcc main.c && ./a.out
steve steve
1000 0
В строке мы пытаемся напечатать имена пользователей steve и root, но печатаетsteve
дважды. В соответствии9
, мы пытаемся напечатать UIDsteve и root, и он их успешно печатает.
Я хочу установить первопричину этого странного поведения в очереди.8
.
Я знаю указатель, возвращаемыйgetpwnam
указывает на статически выделенную память, а на память указывают такие поля, какpw_name/pw_passwd/pw_gecos/pw_dir/pw_shell
также являются статическими, что означает, что эти значения могут быть перезаписаны последующими вызовами. Но все еще смущен этим странным результатом.
Это упражнение 8-1 интерфейса программирования Linux. Добавьте это, чтобы кто-то вроде меня мог найти это через поисковик в будущем :). И вопрос в книге неправильный, зайдите сюда , чтобы увидеть исправленную версию.
3 ответа
Код вызываетgetpwnam()
последовательно возвращает указатель на один и тот же адрес и дважды передает один и тот же указатель. Порядок, в котором компилятор решит выполнять вызовы, будет определять, будет ли отображаться «steve» или «root».
Выделите два буферных пространства и используйте по одному для каждого в вызовеprintf()
позвонивgetpwnam_r()
вместо.
The getpwnam
Функция может возвращать указатель на статические данные, поэтому при каждом вызове она возвращает одно и то же значение указателя. И поскольку вы вызываете эту функцию несколько раз в качестве аргумента дляprintf
, вы увидите только результат того, какой из этих вызовов функции произойдет последним.
Ключевым моментом здесь является то, что порядок вычисления аргументов функции не является упорядоченным , а это означает, что нет никакой гарантии, чтоgetpwnam("steve")
происходит сначала илиgetpwnam("root")
происходит первым.
Результат может быть перезаписан другим вызовом или или . Ваш код демонстрирует это.
См. спецификации POSIX:
Вы не можете контролировать порядок оценки вызовов. Если бы вы сохранили возвращаемые указатели, вы, вероятно, получили бы другие результаты.
POSIX также говорит:
Приложение не должно изменять структуру, на которую указывает возвращаемое значение, а также любые области хранения, на которые указывают указатели внутри структуры. Возвращенный указатель и указатели в структуре могут быть признаны недействительными, или структура или области хранения могут быть перезаписаны последующим вызовом
getpwent()
,getpwnam()
, илиgetpwuid()
. Возвращенный указатель и указатели внутри структуры также могут стать недействительными, если вызывающий поток будет завершен.
Вы должны обрабатывать возвращаемые значения, как если бы они былиconst
- квалифицированный, другими словами.
Обратите внимание, что код в библиотечных функциях не должен перезаписывать предыдущие данные. Например, в macOS Big Sur 11.6.8 следующий код, скомпилированный с-DUSER1=\"daemon\"
как одна из опций компилятора, дает результат:
daemon root
1 0
U1A = 0x7fecfa405fd0, U2A = 0x7fecfa405c60
U1B = 0x7fecfa405fd0, U2B = 0x7fecfa405c60
Модифицированный код:
/* SO 7345-2740 */
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#ifndef USER1
#define USER1 "steve"
#endif
#ifndef USER2
#define USER2 "root"
#endif
int main(void)
{
const struct passwd *user1a = getpwnam(USER1);
const struct passwd *user2a = getpwnam(USER2);
const char *user1_name = user1a->pw_name;
const char *user2_name = user2a->pw_name;
printf("%s %s\n", user1_name, user2_name);
const struct passwd *user1b = getpwnam(USER1);
const struct passwd *user2b = getpwnam(USER2);
int user1_uid = user1b->pw_uid;
int user2_uid = user2b->pw_uid;
printf("%d %d\n", user1_uid, user2_uid);
printf("U1A = %p, U2A = %p\n", (void *)user1a, (void *)user2a);
printf("U1B = %p, U2B = %p\n", (void *)user1b, (void *)user2b);
return EXIT_SUCCESS;
}
Вполне вероятно, что библиотечные функции считывают весь файл в память, а затем передают указатели на соответствующие разделы этой памяти. Конечно, в этом примере указатели на запись для пользователяdaemon
и пользовательroot
стабильны.
YMWV — Ваш пробег будет меняться!