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に想定してない値が入ると思われます…