Странное поведение 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 — Ваш пробег будет меняться!

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