OpenWatcom/未整理/MK_FP
OpenWatcom/未整理/MK_FP
あるいは「16ビット定数・変数を32ビットFARポインタにキャストしたとき、Watcom Cで発生することがある問題について」
このメモ必要かどうかわからない…
MK_FP
16ビットのセグメント値とオフセット値を組み合わせて32ビットのFARポインタを生成する。 上位16ビットにセグメント、下位16ビットにオフセット値が入る。 DOSをターゲットとするたいていのCコンパイラではdos.h内で定義されている(Watcomの場合はi86.hだがdos.hからincludeされているので特に問題はない)。
たとえばVisual C++ 1.0のdos.hでは以下のように定義されている。
#define _MK_FP(seg, offset) (void __far *)(((unsigned long)seg << 16) \ + (unsigned long)(unsigned)offset)
(segmentとoffsetが括弧で囲われていないので微妙に注意が必要かも…)
Turbo C 2.0のdos.hにあるものは微妙に違う。
#define MK_FP(seg,ofs) ((void far *) \ (((unsigned long)(seg) << 16) | (unsigned)(ofs)))
Turbo C++ 1.01のdos.hではだいぶ変更されている。(_seg修飾子はたぶんボーランド独自)
#define MK_FP(seg,ofs) ((void _seg *)(seg) + (void near *)(ofs))
(Open)Watcomの場合i86.hで定義されており、独自演算子 :> を使用している。
#define MK_FP(__s,__o) (((unsigned short)(__s)):>((void __near *)(__o)))
自力でFARポインタを作った場合
諸般の事情で標準搭載のdos.hに頼らず自力でMK_FP相当のことをしたいとき、移植性など考慮するとマイクロソフトかTurbo C 2.0みたいなマクロを組むことになるが、Watcomの場合セグメントが0だと正しいFARポインタが生成されないことがある。
#define MY_MKFPa(s,o) \ (void far *) ( \ ((unsigned long)(s) << 16) | \ (unsigned short)(o) \ ) #define MY_MKFPb(s,o) \ (void far *) ( \ ((unsigned long)(s) << 16) | \ (unsigned long)(unsigned short)(o) \ ) void far *ptr0ai; void far *ptr0av; void far *ptr0bi; void far *ptr0bv; void far *ptr1ai; void far *ptr1av; void far *ptr1bi; void far *ptr1bv; unsigned short ofs16 = 0x1234; int main(void) { ptr0ai = MY_MKFPa(0, 0x1234); ptr0av = MY_MKFPa(0, ofs16); ptr0bi = MY_MKFPb(0, 0x1234); ptr0bv = MY_MKFPb(0, ofs16); ptr1ai = MY_MKFPa(1, 0x1234); ptr1av = MY_MKFPa(1, ofs16); ptr1bi = MY_MKFPb(1, 0x1234); ptr1bv = MY_MKFPb(1, ofs16); return 0; }
wcl -zq -s -d1 -od -c wc_mkfp.cなどとしてobj生成ののち、wdis -a -s=wc_mkfp.c wc_mkfp.objなどとしてasmを得る。
(コンパイル時の -od オプションで最適化を抑止しているため、ポインタ生成時に律儀にORとかしているが、-odオプションを外したデフォルト状態では単なるmovのみに最適化される。ただし行番号情報がずれる)
.387 PUBLIC main_ PUBLIC _ofs16 PUBLIC _ptr1bi PUBLIC _ptr1bv PUBLIC _ptr0bi PUBLIC _ptr1ai PUBLIC _ptr0ai PUBLIC _ptr0bv PUBLIC _ptr1av PUBLIC _ptr0av EXTRN _small_code_:BYTE EXTRN _cstart_:BYTE DGROUP GROUP CONST,CONST2,_DATA,_BSS _TEXT SEGMENT BYTE PUBLIC USE16 'CODE' ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP ; ; #define MY_MKFPa(s,o) \ ; (void far *) ( \ ; ((unsigned long)(s) << 16) | \ ; (unsigned short)(o) \ ; ) ; ; #define MY_MKFPb(s,o) \ ; (void far *) ( \ ; ((unsigned long)(s) << 16) | \ ; (unsigned long)(unsigned short)(o) \ ; ) ; ; void far *ptr0ai; ; void far *ptr0av; ; void far *ptr0bi; ; void far *ptr0bv; ; void far *ptr1ai; ; void far *ptr1av; ; void far *ptr1bi; ; void far *ptr1bv; ; ; unsigned short ofs16 = 0x1234; ; ; int main(void) main_: push bx push cx push dx push si push di push bp mov bp,sp sub sp,2 ; { ; ptr0ai = MY_MKFPa(0, 0x1234); mov word ptr _ptr0ai,1234H mov word ptr _ptr0ai+2,0 ; ptr0av = MY_MKFPa(0, ofs16); mov ax,ds mov dx,word ptr _ofs16 mov word ptr _ptr0av,dx mov word ptr _ptr0av+2,ax ; ptr0bi = MY_MKFPb(0, 0x1234); mov word ptr _ptr0bi,1234H mov word ptr _ptr0bi+2,0 ; ptr0bv = MY_MKFPb(0, ofs16); mov dx,word ptr _ofs16 xor ax,ax mov bx,dx mov dx,ax mov word ptr _ptr0bv,bx mov word ptr _ptr0bv+2,dx ; ptr1ai = MY_MKFPa(1, 0x1234); mov word ptr _ptr1ai,1234H mov word ptr _ptr1ai+2,1 ; ptr1av = MY_MKFPa(1, ofs16); mov dx,word ptr _ofs16 xor ax,ax mov bx,dx mov dx,ax or dl,1 mov ax,dx mov word ptr _ptr1av,bx mov word ptr _ptr1av+2,ax ; ptr1bi = MY_MKFPb(1, 0x1234); mov word ptr _ptr1bi,1234H mov word ptr _ptr1bi+2,1 ; ptr1bv = MY_MKFPb(1, ofs16); mov dx,word ptr _ofs16 xor ax,ax mov bx,dx mov dx,ax or dl,1 mov ax,dx mov word ptr _ptr1bv,bx mov word ptr _ptr1bv+2,ax ; return 0; mov word ptr -2[bp],0 ; } mov ax,word ptr -2[bp] mov sp,bp pop bp pop di pop si pop dx pop cx pop bx ret _TEXT ENDS CONST SEGMENT WORD PUBLIC USE16 'DATA' CONST ENDS CONST2 SEGMENT WORD PUBLIC USE16 'DATA' CONST2 ENDS _DATA SEGMENT WORD PUBLIC USE16 'DATA' _ofs16: DB 34H, 12H _DATA ENDS _BSS SEGMENT WORD PUBLIC USE16 'BSS' ORG 0 _ptr1bi LABEL BYTE ORG 4 _ptr1bv LABEL BYTE ORG 8 _ptr0bi LABEL BYTE ORG 0cH _ptr1ai LABEL BYTE ORG 10H _ptr0ai LABEL BYTE ORG 14H _ptr0bv LABEL BYTE ORG 18H _ptr1av LABEL BYTE ORG 1cH _ptr0av LABEL BYTE ORG 20H _BSS ENDS END
ptr0avのセグメントが 0 でなくて DS になっている。 セグメントが0でない場合やオフセットリテラル整数の場合、またマクロ内でオフセット値をunsigned longでキャストしている場合は意図通りのセグメント値になっている。
余談:nearポインタ、near関数のFARポインタへのキャスト
near関数のエントリをFARポインタにキャストすると、セグメント値として関数のセグメント値が「定数値」として代入される。通常のEXEファイルではロード時のリロケーションにより解決されるため問題ないが、タイニーモデルのCOMファイルにはリロケーション情報がないため、正しいFARポインタは得られない。
いったんポインタ型のstatic変数に代入してその変数をFARポインタにキャストすると、セグメント値としてds(自動変数の場合はss)が代入されるためリロケーション不要になるが、当然ながらCS=DSのタイニーモデル以外では使えない。
.387 PUBLIC nfunc_ PUBLIC main_ PUBLIC _ptr2 PUBLIC _ptr1 PUBLIC _ptr0 PUBLIC _ptra PUBLIC _ptrn EXTRN _small_code_:BYTE EXTRN _cstart_:BYTE DGROUP GROUP CONST,CONST2,_DATA,_BSS _TEXT SEGMENT BYTE PUBLIC USE16 'CODE' ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP ; ; int nfunc(void) nfunc_: push bx push cx push dx push si push di push bp mov bp,sp sub sp,2 ; { ; return 1; mov word ptr -2[bp],1 ; } mov ax,word ptr -2[bp] mov sp,bp pop bp pop di pop si pop dx pop cx pop bx ret ; ; void far *ptra; ; void far *ptr0; ; void far *ptr1; ; void far *ptr2; ; void *ptrn; ; ; int main(void) main_: push bx push cx push dx push si push di push bp mov bp,sp sub sp,4 ; { ; int a; ; ptr0 = (void far *)nfunc; mov word ptr _ptr0+2,seg nfunc_ mov word ptr _ptr0,offset nfunc_ ; ptr1 = (void far *)(char *)(unsigned short)nfunc; mov word ptr _ptr1+2,seg nfunc_ mov word ptr _ptr1,offset nfunc_ ; ptrn = (void *)nfunc; mov word ptr _ptrn,offset nfunc_ ; ptr2 = (void far *)ptrn; mov ax,ds mov dx,word ptr _ptrn mov word ptr _ptr2,dx mov word ptr _ptr2+2,ax ; ptra = (void far *)&a; mov word ptr _ptra+2,ss lea ax,-2[bp] mov word ptr _ptra,ax ; return 0; mov word ptr -4[bp],0 ; } mov ax,word ptr -4[bp] mov sp,bp pop bp pop di pop si pop dx pop cx pop bx ret _TEXT ENDS CONST SEGMENT WORD PUBLIC USE16 'DATA' CONST ENDS CONST2 SEGMENT WORD PUBLIC USE16 'DATA' CONST2 ENDS _DATA SEGMENT WORD PUBLIC USE16 'DATA' _DATA ENDS _BSS SEGMENT WORD PUBLIC USE16 'BSS' ORG 0 _ptr2 LABEL BYTE ORG 4 _ptr1 LABEL BYTE ORG 8 _ptr0 LABEL BYTE ORG 0cH _ptra LABEL BYTE ORG 10H _ptrn LABEL BYTE ORG 12H _BSS ENDS END
余談2:DigitalMars C++のFP_SEGとFP_OFF
ここWatcom関係ないな? 後でなんとかしよう…
FreeDOSのlabelのリポジトリ見てたら、なんかDigitalMars向けのworkaroundというのがコミットされていた。
DigitalMars C++の場合、MK_FPは普通だけどFP_SEGとFP_OFFはdos.h中の定義がちょっとひねってる。
#ifndef __NT__ #define _FP_OFF(fp) (*((unsigned __far *)&(fp))) #define FP_OFF _FP_OFF #if __INTSIZE == 4 extern unsigned __CLIB FP_SEG(void __far *); #define _FP_SEG FP_SEG #else #define _FP_SEG(fp) (*((unsigned __far *)&(fp)+1)) #define FP_SEG _FP_SEG #endif #if __INTSIZE == 4 extern void __far * __CLIB MK_FP(unsigned short,unsigned); #define MK_FP(seg,offset) MK_FP((seg),(unsigned)(offset)) #else #define MK_FP(seg,offset) \ ((void __far *)(((unsigned long)(seg)<<16) | (unsigned)(offset))) #endif #endif #define _MK_FP MK_FP
16ビット部分だけ抜き出すと
#define _FP_OFF(fp) (*((unsigned __far *)&(fp))) #define _FP_SEG(fp) (*((unsigned __far *)&(fp)+1))
たぶん元からfarポインタじゃない(near)ポインタだと、FP_SEGに想定してない値が入ると思われます…