読者です 読者をやめる 読者になる 読者になる

dirname()とbasename()の挙動の確認

現在、社内の人と勉強会をしているLinuxProgramingInterfaceにて出て来たdirname()とbasename()の挙動について、実際に確認してみました。

18章に以下のような文言がありました。

dirname() と basename() はいずれも pathname の文字列を変更する可能性があります。  
このため、pathname を再度利用する場合は、例 18-5 に挙げるようにコピーを作成し、コピーを dirname()、basename() へ渡します。

ここでいうpathnameはdirname() or basename()に与える/xxx/yyyみたいなやつです。

上記がどういうことかと教えてもらったところ、両関数は与えられた文字列を直接書き換えちゃうので、もっかい文字列を参照したい場合はコピーした文字列を両関数に渡してねってことらしいです。

本当に文字列を書き換えてしまうのか確認してみました。

使用したソースコードは、LinuxProgramingInterfaceの中に出て行きた例18-5です。

http://man7.org/tlpi/code/online/dist/dirs_links/t_dirbasename.c.html

gdbで見てみます。
下記は、行数の確認 & 引数の指定 & 引数の確認 & ブレイクポイントの設定です。

(gdb) list
23  #include <libgen.h>
24  #include "tlpi_hdr.h"
25
26  int
27  main(int argc, char *argv[])
28  {
29      char *t1, *t2;
30      int j;
31
32      for (j = 1; j < argc; j++)  {
(gdb) set args /dirname/filename
(gdb) show args
Argument list to give program being debugged when it is started is "/dirname/filename".
(gdb) b 30

実行!
与える文字列は/dirname/filenameとしました。
文字列をt1,t2にコピーして、中身を確認します。
この時点ではt1,t2ともに/dirname/filenameのままみたいです。

(gdb) r
Starting program: /xxxx/t_dirbasename /dirname/filename

Breakpoint 1, main (argc=2, argv=0x7fffffffe648) at t_dirbasename.c:32
32      for (j = 1; j < argc; j++)  {
(gdb) n
33          t1 = strdup(argv[j]);
(gdb) n
34          if (t1 == NULL)
(gdb) n
36          t2 = strdup(argv[j]);
(gdb) n
37          if (t2 == NULL)
(gdb) n
40          printf("%s ==> %s + %s\n", argv[j], dirname(t1), basename(t2));
(gdb) p t1
$1 = 0x603010 "/dirname/filename"
(gdb) p t2
$2 = 0x603030 "/dirname/filename"
(gdb) p *t1
$3 = 47 '/'
(gdb) p *t2
$4 = 47 '/'
(gdb) p *(t1+1)
$5 = 100 'd'
(gdb) p *(t1+2)
$6 = 105 'i'
(gdb) p *(t1+3)
$7 = 114 'r'
(gdb) p *(t1+4)
$8 = 110 'n'
(gdb) p *(t1+5)
$9 = 97 'a'
(gdb) p *(t1+6)
$10 = 109 'm'
(gdb) p *(t1+7)
$11 = 101 'e'
(gdb) p *(t1+8)
$12 = 47 '/'
(gdb) p *(t1+9)
$13 = 102 'f'

さて、問題のdirname(),basename()の実行。
確かにt1は/dirnameになり、t2は/dirname/filenameになりました。
着目すべきは*(t1+8)が /ではなく、\000に書き換えられているところですね! だからt1が/dirnameになるんですね〜。

(gdb) n
/dirname/filename ==> /dirname + filename
42          free(t1);
(gdb) p t1
$14 = 0x603010 "/dirname"
(gdb) p t2
$15 = 0x603030 "/dirname/filename"
(gdb) p *t1
$16 = 47 '/'
(gdb) p *t2
$17 = 47 '/'
(gdb) p *(t1+8)
$18 = 0 '\000'
(gdb) p *(t1+9)
$19 = 102 'f'

もちろん、p t1+9は、filenameになりました。

(gdb) p *(t2+1)
$20 = 100 'd'
(gdb) p t1+9
$21 = 0x603019 "filename"
(gdb) quit

以上、確認終わり!