Cygwinのgccのauto変数

CygwingccでビルドされたCプログラムでは関数に入るごとに勝手に _alloca (aka __chkstk) というアセンブリ・コードが挿入されるなんて知らなかったよ! PSI3のビルドでは libint/emit_order.cvoid emit_order() および libderiv/emit_deriv{1,12}_managers.cvoid emit_deriv{1,12}_managers() においてそのタイミングでSEGVっていた.Insightでデバッグを始めたときは関数に入って最初の変数の初期化より前に,ソースコードには影も形もない _alloca そして probe といったルーチンに入り,その中でSEGVるから,何事かと頭を抱えてしまったよ.普通ならauto変数で長すぎる配列を取るとstack overflowか何かのエラーになると思うが,gccはそこにalloca相当の補助ルーチンを挿入することでエラーにならないようにしてくれるらしい.しかし今回の問題は,それがCygwinではうまく機能してないってことかな? (単にスタックの長さを調べているだけという話もある.まだ理解してない)

/* Function prologue calls _alloca to probe the stack when allocating more
   than CHECK_STACK_LIMIT bytes in one go.  Touching the stack at 4K
   increments is necessary to ensure that the guard pages used
   by the OS virtual memory manger are allocated in correct sequence.  */

	.global ___chkstk
	.global	__alloca
#ifndef _WIN64
___chkstk:
__alloca:
	pushl	%ecx		/* save temp */
	leal	8(%esp), %ecx	/* point past return addr */
	cmpl	$0x1000, %eax	/* > 4k ?*/
	jb	Ldone

Lprobe:
	subl	$0x1000, %ecx  		/* yes, move pointer down 4k*/
	orl	$0x0, (%ecx)   		/* probe there */
	subl	$0x1000, %eax  	 	/* decrement count */
	cmpl	$0x1000, %eax
	ja	Lprobe         	 	/* and do it again */

Ldone:
	subl	%eax, %ecx	   
	orl	$0x0, (%ecx)	/* less than 4k, just peek here */

	movl	%esp, %eax	/* save old stack pointer */
	movl	%ecx, %esp	/* decrement stack */
	movl	(%eax), %ecx	/* recover saved temp */
	movl	4(%eax), %eax	/* recover return address */

	/* Push the return value back.  Doing this instead of just
	   jumping to %eax preserves the cached call-return stack
	   used by most modern processors.  */
	pushl	%eax
	ret
#define MAXNODE 20000
...
void emit_order()
{
...
  hrr_class hrr_nodes[MAXNODE];    /* Stack of HRR nodes */
  vrr_class vrr_nodes[MAXNODE];    /* Stack of VRR nodes */

上に示した長いauto変数の配列をスタックに確保している2行を #ifndef __CYGWIN__ ... #endif で括り,代わりに

#ifdef __CYGWIN__
  hrr_class *hrr_nodes = (hrr_class *)malloc(sizeof(hrr_class)*MAXNODE);
  vrr_class *vrr_nodes = (vrr_class *)malloc(sizeof(vrr_class)*MAXNODE);
#endif

をその下に追加,さらに関数末尾(return; 直前)に

  free(hrr_class);
  free(vrr_class);

を追加. emit_deriv{1,12}_managers.c でも同様にする(略).これで問題が回避できた(これらはPSI3が使う複雑な電子相関積分等を行うC/C++ソースを生成するためのものらしい).