前回は、アセンブラで試行錯誤してエンディアン変換してみました。
先人たちは、どのようにエンディアン変換をプログラミングしているのか気になります。
標準ライブラリのhtonlのソースを読んで、確認したいと思います。
htonlとは、host to networkのバイトオーダー long(32bit)版ですね。
takk@deb9:~$ man htonl
BYTEORDER(3) Linux Programmer's Manual BYTEORDER(3)
名前
htonl, htons, ntohl, ntohs - ホストバイトオーダーとネットワークバイト
オーダーの間で値を変換する
書式
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
説明
htonl() 関数は unsigned integer hostlong を ホストバイトオーダーから
ネットワークバイトオーダーに変換する。
~省略~
ソースを取得。当然glibcですね。
takk@deb9:~/src$ apt-get source glibc ~省略~ takk@deb9:~/src$ cd source glibc-2.24 takk@deb9:~/src/glibc-2.24$
htonlの関数を探します。
takk@deb9:~/src/glibc-2.24$ find -name hton* ./sysdeps/ia64/htonl.S ./sysdeps/ia64/htons.S ./sysdeps/alpha/htonl.S ./sysdeps/alpha/htons.S ./sysdeps/i386/htonl.S ./sysdeps/i386/htons.S ./sysdeps/x86_64/htonl.S ./inet/htons.c ./inet/htontest.c ./inet/htonl.c takk@deb9:~/src/glibc-2.24$
ソース全文です。
takk@deb9:~/src/glibc-2.24$ cat -n inet/htonl.c
1 /* Copyright (C) 1993-2016 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; if not, see
16 <http://www.gnu.org/licenses/>. */
17
18 #include <stdint.h>
19 #include <netinet/in.h>
20
21 #undef htonl
22 #undef ntohl
23
24 uint32_t
25 htonl (uint32_t x)
26 {
27 #if BYTE_ORDER == BIG_ENDIAN
28 return x;
29 #elif BYTE_ORDER == LITTLE_ENDIAN
30 return __bswap_32 (x);
31 #else
32 # error "What kind of system is this?"
33 #endif
34 }
35 weak_alias (htonl, ntohl)
takk@deb9:~/src/glibc-2.24$
内部で、__bswap_32関数を使ってます。(もっと掘り下げると、__builtin_bwap32ですが)
では、この__bswap_32を使って簡単なエンディアン変換プログラムを作ってみます。
takk@deb9:~/tmp$ cat -n t.c
1 #include <stdio.h>
2 #include <stdint.h>
3 #include <netinet/in.h>
4
5 int main()
6 {
7 unsigned long a;
8 unsigned char *p;
9 unsigned char tmp[4];
10 a = 0x12345678;
11 a = __bswap_32(a);
12 printf("%08x\n",a);
13 }
takk@deb9:~/tmp$ ./a.out
78563412
takk@deb9:~/tmp$
エンディアン変換はうまくいきました。
アセンブラを見てみましょう。まずは、main関数。
00000000000006be <main>: 6be: 55 push %rbp 6bf: 48 89 e5 mov %rsp,%rbp 6c2: 48 83 ec 10 sub $0x10,%rsp 6c6: 48 c7 45 f8 78 56 34 movq $0x12345678,-0x8(%rbp) 6cd: 12 6ce: 48 8b 45 f8 mov -0x8(%rbp),%rax 6d2: 89 c7 mov %eax,%edi 6d4: e8 d7 ff ff ff callq 6b0 <__bswap_32> 6d9: 89 c0 mov %eax,%eax 6db: 48 89 45 f8 mov %rax,-0x8(%rbp) 6df: 48 8b 45 f8 mov -0x8(%rbp),%rax 6e3: 48 89 c6 mov %rax,%rsi 6e6: 48 8d 3d 97 00 00 00 lea 0x97(%rip),%rdi # 784 <_IO_stdin_used+0x4> 6ed: b8 00 00 00 00 mov $0x0,%eax 6f2: e8 69 fe ff ff callq 560 <printf@plt> 6f7: b8 00 00 00 00 mov $0x0,%eax 6fc: c9 leaveq 6fd: c3 retq 6fe: 66 90 xchg %ax,%ax
6d4行で__bswap_32がコールされています。__bswap_32のアセンブラについても見てみます。
00000000000006b0 <__bswap_32>: 6b0: 55 push %rbp 6b1: 48 89 e5 mov %rsp,%rbp 6b4: 89 7d fc mov %edi,-0x4(%rbp) 6b7: 8b 45 fc mov -0x4(%rbp),%eax 6ba: 0f c8 bswap %eax 6bc: 5d pop %rbp 6bd: c3 retq
とても少ないコードです。よく見ると、bswapという命令を使ってますね。 これを使えば、簡単にエンディアン変換してくれそうです。
では、bswap命令を、Cプログラムに直接埋め込んで使ってみましょう。
takk@deb9:~/tmp$ cat -n t.c
1 #include <stdio.h>
2 int main()
3 {
4 unsigned long a;
5 asm("movq $0x12345678,-0x8(%rbp)");
6 asm("mov -0x8(%rbp), %eax");
7 asm("bswap %eax");
8 asm("mov %eax,-0x8(%rbp)");
9 printf("%08x\n",a);
10 }
takk@deb9:~/tmp$
実行してみます。
takk@deb9:~/tmp$ gcc t.c takk@deb9:~/tmp$ ./a.out 78563412 takk@deb9:~/tmp$
エンディアン変換って、CPUに命令が存在すれば、ものすごく簡単にできてしまうものなんですね。


コメント