1

ここのNetBSD実装を見てexecvpください:

http://cvsweb.netbsd.se/cgi-bin/bsdweb.cgi/src/lib/libc/gen/execvp.c?rev=1.30.16.2;content-type=text%2Fplain

処理の特別な場合の130行目のコメントに注意してくださいENOEXEC

/*
 * we can't use malloc here because, if we are doing
 * vfork+exec, it leaks memory in the parent.
 */
if ((memp = alloca((cnt + 2) * sizeof(*memp))) == NULL)
     goto done;
memp[0] = _PATH_BSHELL;
memp[1] = bp;
(void)memcpy(&memp[2], &argv[1], cnt * sizeof(*memp));
(void)execve(_PATH_BSHELL, __UNCONST(memp), environ);
goto done;

この実装execvpをスタンドアロンC++に移植しようとしています。 alloca非標準なので避けたいです。(実際、私が欲しい関数はexecvpeFreeBSDのものですが、これは問題をより明確に示しています。)

プレーンが使用された場合にメモリがリークする理由を理解していると思いますmalloc-の呼び出し元はexecvp親でコードを実行できますが、への内部呼び出しはexecve決して返されないため、関数はポインタを解放できずmemp、ポインタをに戻す方法はありません発信者。しかし、私は置き換える方法を考えることができませんalloca-このメモリリークを回避するために必要な魔法のようです。C99は可変長配列を提供していると聞きましたが、最終的なターゲットはC ++であるため、残念ながら使用できません。

この使用を置き換えることは可能allocaですか?C ++ / POSIX内にとどまることが義務付けられている場合、このアルゴリズムを使用するときに避けられないメモリリークはありますか?

4

2 に答える 2

0

編集:マイケルがコメントで指摘しているように、以下に書かれていることは、最適化コンパイラによるスタック相対アドレス指定のため、実際には機能しません。したがって、本番レベルallocaでは、実際に「動作」するためにコンパイラの助けが必要です。allocaしかし、うまくいけば、以下のコードは、内部で何が起こっているのか、そして心配するスタック相対アドレス指定の最適化がなかった場合にどのような関数が機能したかについてのいくつかのアイデアを与えることができます。

ところで、自分で簡単なバージョンを作成する方法に興味があった場合に備えallocaて、その関数は基本的にスタックに割り当てられたスペースへのポインタを返すため、スタックを適切に操作できる関数をアセンブリに記述できます。呼び出し元の現在のスコープで使用できるポインターを返します(呼び出し元が戻ると、呼び出し元allocaからの戻りがスタックをクリーンアップするため、このバージョンのスタックスペースポインターは無効になります)。

Unix 64ビットABIを使用するx86_64プラットフォームでLinuxのフレーバーを使用していると仮定して、「my_alloca.s」というファイル内に以下を配置します。

.section .text
.global my_alloca

my_alloca:
    movq (%rsp), %r11       # save the return address in temp register
    subq %rdi, %rsp         # allocate space on stack from first argument
    movq $0x10, %rax
    negq %rax
    andq %rax, %rsp         # align the stack to 16-byte boundary
    movq %rsp, %rax         # save address in return register
    pushq %r11              # push return address on stack
    ret                     # return back to caller

次に、C / C ++コードモジュール(つまり、「。cpp」ファイル)内で、次のように使用できます。

extern my_alloca(unsigned int size);

void function()
{
    void* stack_allocation = my_alloca(BUFFERSIZE);
    //...do something with the allocated space

    return; //WARNING: stack_allocation will be invalid after return
}

を使用して「my_alloca.s」をコンパイルできますgcc -c my_alloca.s。これにより、「my_alloca.o」という名前のファイルが作成されます。このファイルを使用して、を使用gcc -oまたは使用して他のオブジェクトファイルとリンクできますld

この実装で私が考えることができる主な「落とし穴」は、アクティベーションレコードとスタックベースポインタ(つまり、RBPx86_64のポインタ)ですが、関数呼び出しごとに明示的に割り当てられたメモリです。次に、コンパイラはスタックに割り当てられたメモリを認識しないため、呼び出し元のリターンでスタックをクリーンアップし、プッシュされた呼び出し元のリターンアドレスであると信じているものを使用してジャンプバックしようとします。関数呼び出しの開始時にスタック上で、no-wheres-villeを指している命令ポインターにジャンプし、バスエラーまたはある種のアクセスエラーでクラッシュする可能性があります。許可されていないメモリ位置でコードを実行します。

コンパイラがスタックスペースを使用して引数を割り当てる場合(引数が1つしかないため、Unix 64ビットABIごとにこの関数を使用するべきではありません)など、実際には他にも危険なことが発生する可能性があります。関数呼び出しの直後のスタックのクリーンアップ。ポインターの有効性を台無しにします。しかしexecvp()、エラーが発生しない限り戻らないのような関数を使用すれば、これはそれほど問題にはならないはずです。

全体として、このような機能はプラットフォームに依存します。

于 2011-05-23T20:25:05.760 に答える
0

への呼び出しを、への呼び出しのallocamalloc行われたへの呼び出しに置き換えることができます。呼び出し元に戻った後、メモリを削除できます。( が呼び出されて新しいプログラムが開始されるまで戻らないため、これは安全です。) 呼び出し元は、malloc で割り当てたメモリを解放できます。vforkvforkvforkexec

exec呼び出しによって子イメージが親プロセスのイメージに完全に置き換えられ、フォークされたプロセスが保持していたメモリが暗黙的に解放されるため、これによって子のメモリがリークすることはありません。

fork別の考えられる解決策は、の代わりにに切り替えることですvforkforkこれには、呼び出しが完了する前に戻るため、呼び出し元に少し余分なコードexecが必要になるため、呼び出し元はそれを待つ必要があります。しかし、一度forked新しいプロセスをmalloc安全に使用できます。私の理解では、カーネルがコピーオンライトページを持つ前の時代には高価だったのでvfork、それは基本的に貧乏人のものだったということです。最新のカーネルは非常に効率的に実装されており、多少危険な方法に頼る必要はありません。forkforkforkvfork

于 2011-05-23T23:40:41.937 に答える