fclose работает по-разному на Android и Linux

Следующая программа:

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

int main() {
  fclose( stderr );
  printf( "%d\n", fileno( stderr ) );
  return 0;
}

шоу -1 на Ubuntu 11.04 и 2 на эмуляторе ICS 4.0.3. Не могу найти информацию об этой проблеме - могу ли я заставить этот код работать одинаково на обеих платформах? freopen на stderr имеет ту же проблему.

Обновить:

Предыдущая небольшая программа демонстрирует причину реальной проблемы, с которой я столкнулся: если я пытаюсь freopenstderr подать в несуществующий каталог, на linux stderr закрыто но на андроиде остается открытым! И даже больше - если я напишу что-то в этом открытом stderr файл, а затем сделать fopen в другом файле - текст, который я напечатал stderr записывается в этот открытый файл.

Итак, эта программа:

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>

# define LOGD( ... ) printf( __VA_ARGS__ ); printf( "\n" )

# ifdef ANDROID
#   define HOMEDIR "/data/data/com.myapp/" // for android
# else
#   define HOMEDIR "/home/darkmist/" // for linux
# endif

# define _T( x ) x

void TestFreopen_mkdir() {
  int mkdirres = mkdir( HOMEDIR "1.d", 0777 );
  LOGD(_T("TestFreopen mkdirres=0x%08x"),mkdirres);
}

void TestFreopen() {
  LOGD(_T("TestFreopen begin"));

  LOGD(_T("TestFreopen stderr=0x%08x"),fileno(stderr));
  fprintf(stderr,"fprintf_1 to stderr\n");

  // TestFreopen_mkdir(); // case 1

  if ( NULL == freopen( HOMEDIR "1.d/1", "w", stderr ) ) {
    LOGD( "freopen failed" );
    if ( -1 != fileno( stderr ) ) {
      fclose( stderr );
      LOGD( "freopen closed" );
    }
  }

  LOGD(_T("TestFreopen stderr=0x%08x"),fileno(stderr));
  fprintf(stderr,"fprintf_2 to stderr\n");

  TestFreopen_mkdir(); // case 2

  FILE* fopen_file = fopen( HOMEDIR "1.d/2", _T( "wb" ) );

  LOGD(_T("TestFreopen fopen_file=0x%08x"),fileno(fopen_file)); // same as for reopened stderr!!

  fprintf(stderr,"fprintf_3 to stderr\n");
  fprintf(fopen_file,"fprintf_1 to fopen_file\n");
  fflush(fopen_file);

  LOGD(_T("TestFreopen end"));
}

int main() {
  TestFreopen();
  return 0;
}

показывает это на Linux:

$ ./a.out
TestFreopen begin
TestFreopen stderr=0x00000002
fprintf_1 to stderr
freopen failed
TestFreopen stderr=0xffffffff
TestFreopen mkdirres=0x00000000
TestFreopen fopen_file=0x00000002
TestFreopen end

$ cat ~/1.d/2 
fprintf_1 to fopen_file

а это на андроиде

$ adb push ./a.out /data/data/com.myapp
573 KB/s (34635 bytes in 0.058s)

$ adb shell run-as com.myapp /data/data/com.myapp/a.out
TestFreopen begin
TestFreopen stderr=0x00000002
fprintf_1 to stderr
freopen failed
freopen closed
TestFreopen stderr=0x00000002
TestFreopen mkdirres=0x00000000
TestFreopen fopen_file=0x00000002
TestFreopen end

$ adb shell run-as com.myapp cat /data/data/com.myapp/1.d/2
fprintf_3 to stderr
fprintf_1 to fopen_file

3 ответа

Решение

Нет смысла пытаться использовать stderr после его закрытия на любой платформе.

Мне кажется, что вы ожидаете, что формально объявленное неопределенное поведение указателя файла после его закрытия будет одинаковым на устройствах с очень разными библиотеками Си.

После того, как вы вызовете freopen() способом, который предназначен для сбоя, будущие попытки использовать этот указатель файла не будут зависеть от получения последовательного результата.

Вы нашли некоторые интересные результаты, благодаря которым оставшиеся части могут все еще использоваться с неожиданным результатом, но проблема не в том, что они не вызывают каких-то исключений, а в том, что вы пытаетесь использовать недопустимый указатель файла на языке это не рекламирует себя как наличие гарантий.

Проблема не в системе, а в неправильном использовании вашей программой системы.

fclose() является функцией, предоставляемой libc, поэтому разные реализации libc могут иметь разное поведение, так как состояние файлового дескриптора после того, как fclose() не определено

Ubuntu использует eglibc, в то время как android использует bionic в качестве стандартного libc.


eglibc

Глядя на исходный код eglibc 2.15 для fclose(), мы имеем:

от iofclose.c:

...
_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    status = INTUSE(_IO_file_close_it) (fp);
else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
...

Вы закрываете strerr, первый оператор будет выполнен. Глядя на следующую функцию мы имеем:

из fileops.c:

fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;

return close_status ? close_status : write_status;

Как видите, eglibc явно установит fileno в -1.


бионический

Bionic обрабатывает файлы по-своему, начиная с fileno (), которая возвращает файл fp->_:

из stdio.h

#define __sfileno(p)    ((p)->_file)

из fileno.c

int
fileno(FILE *fp)
{
        int ret;

    FLOCKFILE(fp);
        ret = __sfileno(fp);
        FUNLOCKFILE(fp);
        return (ret);
}

Глядя на бионический исходный код для fclose(), мы имеем:

от fclose.c

int
fclose(FILE *fp)
{
        int r;

        if (fp->_flags == 0) {  /* not open! */
                errno = EBADF;
                return (EOF);
        }
        FLOCKFILE(fp);
        WCIO_FREE(fp);
        r = fp->_flags & __SWR ? __sflush(fp) : 0;
        if (fp->_close != NULL && (*fp->_close)(fp->_cookie) < 0)
                r = EOF;
        if (fp->_flags & __SMBF)
                free((char *)fp->_bf._base);
        if (HASUB(fp))
                FREEUB(fp);
        if (HASLB(fp))
                FREELB(fp);
        fp->_r = fp->_w = 0;    /* Mess up if reaccessed. */
        fp->_flags = 0;         /* Release this FILE for reuse. */
        FUNLOCKFILE(fp);
        return (r);
}

от wcio.h

#define WCIO_FREE(fp) ((void)(0))

от local.h

#define HASUB(fp) (_UB(fp)._base != NULL)
#define FREEUB(fp) { \
        if (_UB(fp)._base != (fp)->_ubuf) \
                free(_UB(fp)._base); \
        _UB(fp)._base = NULL; \
}

Как видите, файл fp->_ остался нетронутым во время процесса fclose(). На самом деле это не ошибка, так как состояние дескриптора файла после fclose() не определено, но это должно прояснить разницу между выполнением вашей программы в Ubuntu и в Android.

Чтобы сделать ваш код переносимым, вы должны избегать закрытия stderr или заново открывать его, например, /dev/null после его закрытия.

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