从一些现象看printf的缓冲机制
一、C库的printf函数簇
这些函数其实大家最为熟悉,因为每个人都会写的hello world就是使用了printf这个C库函数。但是printf的实现并不见,如果有兴趣的同学可以看一下glibc中关于这个函数的哦实现,先不说各种格式化的处理以及文件的锁,其中的缓冲区管理及动态资源管理就有相当多的代码。
这里主要是想通过一些现象来看一下printf函数的缓冲区机制可能造成的一些看起来比较奇怪的问题。
二、缓冲区的大小
每个FILE结构的缓冲区大小在文件创建时分配,分配的函数在glibc-2.11.2\libio:filedoalloc.c
size = _IO_BUFSIZ; 其中这个宏的值默认为8192
if (fp->_fileno >= 0 && __builtin_expect (_IO_SYSSTAT (fp, &st), 0) >= 0)
{
if (S_ISCHR (st.st_mode))
{
/* Possibly a tty. */
if (
#ifdef DEV_TTY_P
DEV_TTY_P (&st) ||
#endif
isatty (fp->_fileno))
fp->_flags |= _IO_LINE_BUF;
}
#if _IO_HAVE_ST_BLKSIZE
if (st.st_blksize > 0)
size = st.st_blksize;
#endif
}
这里比较有意思的是对于文件类型的识别,如果文件类型是终端设备,那么此时为使能行缓冲机制,行缓冲机制区别于默认的片缓冲区机制,前者是在遇到一个回车时一定进行缓冲区的冲刷,而对于后则则在整个缓冲区满了之后才会输出。对于这个行缓冲区标识的使用位置在glibc-2.11.2\libio\fileops.c
int
_IO_new_file_overflow (f, ch)
_IO_FILE *f;
int ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (INTUSE(_IO_do_write) (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
这里的缓冲区分配并不是在fopen的时候分配,而是在真正print的时候根据需要创建和分配,在glibc2.11的调用链为
(gdb) bt
#0 _IO_file_doallocate (fp=<value optimized out>) at filedoalloc.c:117
#1 0x0804cc7f in _IO_doallocbuf (fp=<value optimized out>) at genops.c:423
#2 0x0804bb91 in _IO_new_file_overflow (f=<value optimized out>,
ch=<value optimized out>) at fileops.c:842
#3 0x0804c636 in __overflow (f=<value optimized out>,
ch=<value optimized out>) at genops.c:248
#4 0x0806744d in _IO_vfprintf_internal (s=<value optimized out>,
format=<value optimized out>, ap=<value optimized out>) at vfprintf.c:1592
#5 0x08048f75 in __printf (format=<value optimized out>) at printf.c:35
#6 0x080482be in main () at buffer.c:16
(gdb)
三、验证设备对缓冲区的影响
1、使用管道
[root@Harry filebuff]# cat typical.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
for ( i = 1; i > 0 ; i++)
{
sleep(1);
printf("%1024d",i);
}
}
[root@Harry filebuff]# gcc typical.c -o typical
[root@Harry filebuff]# ./typical | tee
执行此命令之后,可以看到,在输出中是按照每4个数值(1024*4=4096)字节来一次性输出的,我们看一下管道的缓冲区大小
[root@Harry filebuff]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 8192
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[root@Harry filebuff]#
2、伪终端
[root@Harry filebuff]# stat /proc/self/fd/1
File: `/proc/self/fd/1' -> `/dev/pts/5'
Size: 64 Blocks: 0 IO Block: 1024 symbolic link
Device: 3h/3d Inode: 111229 Links: 1
Access: (0700/lrwx------) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2013-08-05 22:54:42.070907990 +0800
Modify: 2013-08-05 22:54:42.070907990 +0800
Change: 2013-08-05 22:54:42.070907990 +0800
[root@Harry filebuff]# ./typical
输出以1为单位输出,即1024字节为缓冲区。
3、普通文件
[root@Harry filebuff]# ./typical > typical.out &
[1] 15287
[root@Harry filebuff]# tail -f typical.out
[root@Harry filebuff]# stat typical.out
File: `typical.out'
Size: 16384 Blocks: 32 IO Block: 4096 regular file
Device: fd00h/64768d Inode: 529035 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2013-08-05 22:56:28.999031207 +0800
Modify: 2013-08-05 22:56:33.015530737 +0800
Change: 2013-08-05 22:56:33.015530737 +0800
也是以8KB为单位。
四、缓冲区是否会丢失
既然进程进行了缓冲,那么会不会进程退出之后这些信息丢失呢?
1、正常情况下
不会丢失,因为C库会将进程所有打开的FILE结构组成一个链表,在exit之后执行这些文件的缓冲区冲刷清理工作,所以不会丢失。
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
genops.c
INTDEF(_IO_un_link)
void
_IO_link_in (fp)
struct _IO_FILE_plus *fp;
{
if ((fp->file._flags & _IO_LINKED) == 0)
{
fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
fp->file._chain = (_IO_FILE *) INTUSE(_IO_list_all);
INTUSE(_IO_list_all) = fp;
++_IO_list_all_stamp;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
2、异常情况
大家可以看到,这个实现是依赖于在main函数退出之后执行,在exit的代码中执行冲刷,如果进程不幸是被kill掉或者其它信号导致的问题,那么此时输出就真的丢失了。
这一点和操作系统的缓写机制不同,后者在进程被kill掉依然会写回硬盘,只要没有断点。而对于printf来说,它的缓存对操作系统来说透明,所以异常终止就丢失输出。
[root@Harry filebuff]# cat buffer.c
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
void sighandler(int sig)
{
printf("sighand %d\n", sig);
exit(0);
}
int main()
{
signal(SIGINT,sighandler);
FILE * file = fopen("/dev/tty","wr");
printf("%p\n",file);
int icount = fprintf(file,"sdfksdkfsdkfsd");
sleep(1000);
printf("after before\n");
}
[root@Harry filebuff]# gcc buffer.c -o buffer
[root@Harry filebuff]# ./buffer
0x9c4b008
^Csighand 2
sdfksdkfsdkfsd[root@Harry filebuff]#
可以看到,如果捕捉了一场信号,此时缓冲区中的内容会在最后被打印出来。但是如果是通过其它没有被注册的信号杀死进程,则内容丢失。
sdfksdkfsdkfsd[root@Harry filebuff]# ./buffer &
0x922e008
[2] 15361
[root@Harry filebuff]# kill -TERM 15361
[root@Harry filebuff]# kill -TERM 15361
bash: kill: (15361) - No such process
[2]+ Terminated ./buffer
[root@Harry filebuff]#
五、缓冲区的独立性
我们知道,通常进程在启动的时候,stdin,stdout,stderr是三个与创建的FILE结构,虽然通常它们对应的是同一个设备,但是由于它们使用的是各自的缓冲区,所以它们在代码里出现的顺序和最终看到的顺序可能并不相同。
例如我们如果将标准输入和输出都重定向到相同的文件,那么文件的最终顺序和输出顺序同样可能不同。
[root@Harry filebuff]# cat fork.c
#include <stdio.h>
int main()
{
int i;
for (i = 0; i < 1024; i++)
{
printf("stdin");
fprintf(stderr,"stderr");
}
}
[root@Harry filebuff]# gcc fork.c -o fork
[root@Harry filebuff]# ./fork >fork.ort 2>&1 虽然同一个文件,但是由于使用独立缓冲区,所以输出是大杂居,小聚居的分布。
[root@Harry filebuff]# cat fork.ort
stderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstder
六、一个grep缓冲的例子
[root@Harry filebuff]# ( for((loop = 0 ; loop<200; loop++)) do echo int; done ; yes ) | grep int | more
grep默认是不会处理缓冲区的,也即是使用C库的默认缓冲机制,通过上面的例子可以看到,这个命令始终不会有输出,如果去掉最后的管道,则马上会有输出。因为去掉管道之后,此时输出为一个tty设备,此时tty使用行缓冲,每个匹配都会打印出来。
而对于管道,它缓冲区大小为4KB,所以虽然有200个匹配项,但是输出内容没有达到4KB,所以还是没有输出。不过grep有一个使用行缓冲的选项,
[root@Harry filebuff]# ( for((loop = 0 ; loop<200; loop++)) do echo int; done ; yes ) | grep int --line-buffered | more
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
--More--
这些函数其实大家最为熟悉,因为每个人都会写的hello world就是使用了printf这个C库函数。但是printf的实现并不见,如果有兴趣的同学可以看一下glibc中关于这个函数的哦实现,先不说各种格式化的处理以及文件的锁,其中的缓冲区管理及动态资源管理就有相当多的代码。
这里主要是想通过一些现象来看一下printf函数的缓冲区机制可能造成的一些看起来比较奇怪的问题。
二、缓冲区的大小
每个FILE结构的缓冲区大小在文件创建时分配,分配的函数在glibc-2.11.2\libio:filedoalloc.c
size = _IO_BUFSIZ; 其中这个宏的值默认为8192
if (fp->_fileno >= 0 && __builtin_expect (_IO_SYSSTAT (fp, &st), 0) >= 0)
{
if (S_ISCHR (st.st_mode))
{
/* Possibly a tty. */
if (
#ifdef DEV_TTY_P
DEV_TTY_P (&st) ||
#endif
isatty (fp->_fileno))
fp->_flags |= _IO_LINE_BUF;
}
#if _IO_HAVE_ST_BLKSIZE
if (st.st_blksize > 0)
size = st.st_blksize;
#endif
}
这里比较有意思的是对于文件类型的识别,如果文件类型是终端设备,那么此时为使能行缓冲机制,行缓冲机制区别于默认的片缓冲区机制,前者是在遇到一个回车时一定进行缓冲区的冲刷,而对于后则则在整个缓冲区满了之后才会输出。对于这个行缓冲区标识的使用位置在glibc-2.11.2\libio\fileops.c
int
_IO_new_file_overflow (f, ch)
_IO_FILE *f;
int ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (INTUSE(_IO_do_write) (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
这里的缓冲区分配并不是在fopen的时候分配,而是在真正print的时候根据需要创建和分配,在glibc2.11的调用链为
(gdb) bt
#0 _IO_file_doallocate (fp=<value optimized out>) at filedoalloc.c:117
#1 0x0804cc7f in _IO_doallocbuf (fp=<value optimized out>) at genops.c:423
#2 0x0804bb91 in _IO_new_file_overflow (f=<value optimized out>,
ch=<value optimized out>) at fileops.c:842
#3 0x0804c636 in __overflow (f=<value optimized out>,
ch=<value optimized out>) at genops.c:248
#4 0x0806744d in _IO_vfprintf_internal (s=<value optimized out>,
format=<value optimized out>, ap=<value optimized out>) at vfprintf.c:1592
#5 0x08048f75 in __printf (format=<value optimized out>) at printf.c:35
#6 0x080482be in main () at buffer.c:16
(gdb)
三、验证设备对缓冲区的影响
1、使用管道
[root@Harry filebuff]# cat typical.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
for ( i = 1; i > 0 ; i++)
{
sleep(1);
printf("%1024d",i);
}
}
[root@Harry filebuff]# gcc typical.c -o typical
[root@Harry filebuff]# ./typical | tee
执行此命令之后,可以看到,在输出中是按照每4个数值(1024*4=4096)字节来一次性输出的,我们看一下管道的缓冲区大小
[root@Harry filebuff]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 8192
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[root@Harry filebuff]#
2、伪终端
[root@Harry filebuff]# stat /proc/self/fd/1
File: `/proc/self/fd/1' -> `/dev/pts/5'
Size: 64 Blocks: 0 IO Block: 1024 symbolic link
Device: 3h/3d Inode: 111229 Links: 1
Access: (0700/lrwx------) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2013-08-05 22:54:42.070907990 +0800
Modify: 2013-08-05 22:54:42.070907990 +0800
Change: 2013-08-05 22:54:42.070907990 +0800
[root@Harry filebuff]# ./typical
输出以1为单位输出,即1024字节为缓冲区。
3、普通文件
[root@Harry filebuff]# ./typical > typical.out &
[1] 15287
[root@Harry filebuff]# tail -f typical.out
[root@Harry filebuff]# stat typical.out
File: `typical.out'
Size: 16384 Blocks: 32 IO Block: 4096 regular file
Device: fd00h/64768d Inode: 529035 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2013-08-05 22:56:28.999031207 +0800
Modify: 2013-08-05 22:56:33.015530737 +0800
Change: 2013-08-05 22:56:33.015530737 +0800
也是以8KB为单位。
四、缓冲区是否会丢失
既然进程进行了缓冲,那么会不会进程退出之后这些信息丢失呢?
1、正常情况下
不会丢失,因为C库会将进程所有打开的FILE结构组成一个链表,在exit之后执行这些文件的缓冲区冲刷清理工作,所以不会丢失。
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
genops.c
INTDEF(_IO_un_link)
void
_IO_link_in (fp)
struct _IO_FILE_plus *fp;
{
if ((fp->file._flags & _IO_LINKED) == 0)
{
fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
fp->file._chain = (_IO_FILE *) INTUSE(_IO_list_all);
INTUSE(_IO_list_all) = fp;
++_IO_list_all_stamp;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
2、异常情况
大家可以看到,这个实现是依赖于在main函数退出之后执行,在exit的代码中执行冲刷,如果进程不幸是被kill掉或者其它信号导致的问题,那么此时输出就真的丢失了。
这一点和操作系统的缓写机制不同,后者在进程被kill掉依然会写回硬盘,只要没有断点。而对于printf来说,它的缓存对操作系统来说透明,所以异常终止就丢失输出。
[root@Harry filebuff]# cat buffer.c
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
void sighandler(int sig)
{
printf("sighand %d\n", sig);
exit(0);
}
int main()
{
signal(SIGINT,sighandler);
FILE * file = fopen("/dev/tty","wr");
printf("%p\n",file);
int icount = fprintf(file,"sdfksdkfsdkfsd");
sleep(1000);
printf("after before\n");
}
[root@Harry filebuff]# gcc buffer.c -o buffer
[root@Harry filebuff]# ./buffer
0x9c4b008
^Csighand 2
sdfksdkfsdkfsd[root@Harry filebuff]#
可以看到,如果捕捉了一场信号,此时缓冲区中的内容会在最后被打印出来。但是如果是通过其它没有被注册的信号杀死进程,则内容丢失。
sdfksdkfsdkfsd[root@Harry filebuff]# ./buffer &
0x922e008
[2] 15361
[root@Harry filebuff]# kill -TERM 15361
[root@Harry filebuff]# kill -TERM 15361
bash: kill: (15361) - No such process
[2]+ Terminated ./buffer
[root@Harry filebuff]#
五、缓冲区的独立性
我们知道,通常进程在启动的时候,stdin,stdout,stderr是三个与创建的FILE结构,虽然通常它们对应的是同一个设备,但是由于它们使用的是各自的缓冲区,所以它们在代码里出现的顺序和最终看到的顺序可能并不相同。
例如我们如果将标准输入和输出都重定向到相同的文件,那么文件的最终顺序和输出顺序同样可能不同。
[root@Harry filebuff]# cat fork.c
#include <stdio.h>
int main()
{
int i;
for (i = 0; i < 1024; i++)
{
printf("stdin");
fprintf(stderr,"stderr");
}
}
[root@Harry filebuff]# gcc fork.c -o fork
[root@Harry filebuff]# ./fork >fork.ort 2>&1 虽然同一个文件,但是由于使用独立缓冲区,所以输出是大杂居,小聚居的分布。
[root@Harry filebuff]# cat fork.ort
stderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstderrstder
六、一个grep缓冲的例子
[root@Harry filebuff]# ( for((loop = 0 ; loop<200; loop++)) do echo int; done ; yes ) | grep int | more
grep默认是不会处理缓冲区的,也即是使用C库的默认缓冲机制,通过上面的例子可以看到,这个命令始终不会有输出,如果去掉最后的管道,则马上会有输出。因为去掉管道之后,此时输出为一个tty设备,此时tty使用行缓冲,每个匹配都会打印出来。
而对于管道,它缓冲区大小为4KB,所以虽然有200个匹配项,但是输出内容没有达到4KB,所以还是没有输出。不过grep有一个使用行缓冲的选项,
[root@Harry filebuff]# ( for((loop = 0 ; loop<200; loop++)) do echo int; done ; yes ) | grep int --line-buffered | more
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
int
--More--