1

私はこの単純なCプログラムを持っています:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main (int argc, char **argv) {
    int i = 0;
    int j = 0;
    size_t size = 4194304; /* 4 MiB */
    char *buffer = malloc(size);
    char *buffers[10] = {NULL};
    void *tmp_pointer = NULL;
    fprintf(stderr, "initial size == %zu\n", size);
    fprintf(stderr, "initial buffer == %p\n\n", buffer);
    srand(time(NULL));
    /* let's see when it fails ... */
    for (; i < 10; ++i) {
        /* some random writes */
        for (j = 0; j < 1000; ++j) {
            buffer[rand() % size] = (char)(rand());
        }
        /* some interleaving memory allocations */
        buffers[i] = malloc(1048576); /* 1 MiB */
        size *= 2;
        fprintf(stderr, "new size == %zu\n", size);
        tmp_pointer = realloc(buffer, size);
        if ((tmp_pointer == NULL) || (errno != 0)) {
            fprintf(stderr, "tmp_pointer == %p\n", tmp_pointer);
            fprintf(stderr, "errno == %d\n", errno);
            perror("realloc");
            return (1);
        } else {
            buffer = tmp_pointer;
        }
        fprintf(stderr, "new buffer == %p\n\n", buffer);
    }
    fprintf(stderr, "Trying to free the buffers.\n");
    free(buffer);
    if (errno != 0) {
        fprintf(stderr, "errno == %d\n", errno);
        perror("free(buffer)");
        return (2);
    }
    for (i = 0; i < 10; ++i) {
        free(buffers[i]);
        if (errno != 0) {
            fprintf(stderr, "i == %d\n", i);
            fprintf(stderr, "errno == %d\n", errno);
            perror("free(buffers)");
            return (3);
        }
    }
    fprintf(stderr, "Successfully freed.\n");
    return (0);
}

4 MiB のメモリを割り当てるだけで、再割り当てによってそのサイズを 2 倍にしようと 10 回試みます。realloc への単純な呼び出しは、ヒープ アロケーターの「トリック」を最小限に抑えるために、1 MiB ブロックの別の割り当てといくつかのランダム書き込みでインターリーブされます。16 GiB RAM を搭載した Ubuntu Linux マシンでは、次の出力が得られます。

./realloc_test
initial size == 4194304
initial buffer == 0x7f3604c81010

new size == 8388608
new buffer == 0x7f3604480010

new size == 16777216
new buffer == 0x7f360347f010

new size == 33554432
new buffer == 0x7f360147e010

new size == 67108864
new buffer == 0x7f35fd47d010

new size == 134217728
new buffer == 0x7f35f547c010

new size == 268435456
new buffer == 0x7f35e547b010

new size == 536870912
new buffer == 0x7f35c547a010

new size == 1073741824
new buffer == 0x7f3585479010

new size == 2147483648
new buffer == 0x7f3505478010

new size == 4294967296
new buffer == 0x7f3405477010

Trying to free the buffers.
Successfully freed.

そのため、4 GiB までのすべての再割り当ては成功しているように見えます。ただし、次のコマンドでカーネル仮想メモリのアカウンティング モードを 2 (常にチェックし、オーバーコミットしない) に設定すると、次のようになります。

echo 2 > /proc/sys/vm/overcommit_memory

出力は次のように変わります。

./realloc_test
initial size == 4194304
initial buffer == 0x7fade1fa7010

new size == 8388608
new buffer == 0x7fade17a6010

new size == 16777216
new buffer == 0x7fade07a5010

new size == 33554432
new buffer == 0x7fadde7a4010

new size == 67108864
new buffer == 0x7fadda7a3010

new size == 134217728
new buffer == 0x7fadd27a2010

new size == 268435456
new buffer == 0x7fadc27a1010

new size == 536870912
new buffer == 0x7fada27a0010

new size == 1073741824
new buffer == 0x7fad6279f010

new size == 2147483648
tmp_pointer == (nil)
errno == 12
realloc: Cannot allocate memory

再配置は 2 GiB で失敗します。によって報告された時点でのコンピューターの空きメモリはtop約 5 GiB であり、realloc は常にメモリの連続ブロックを割り当てる必要があるため、妥当です。ここで、同じマシンの VirtualBox 内で Mac OS X Lion で同じプログラムを実行するとどうなるか見てみましょう。仮想 RAM は 8 GiB しかありません。

./realloc_test
initial size == 4194304
initial buffer == 0x101c00000

new size == 8388608
tmp_pointer == 0x102100000
errno == 22
realloc: Invalid argument

ここで、このプログラムには、最初の 8 MiB への再割り当てに問題があります。私の意見では、これは非常に奇妙topです.

しかし、実際には realloc が成功したのは、その戻り値が非 NULL であるためです (tmp_pointerプログラム終了の直前の値に注意してください)。しかし、まったく同じように realloc の呼び出しが成功すると、errno もゼロ以外に設定されます。さて、この状況を処理する正しい方法は何ですか?

errno を無視して、realloc からの戻り値のみをチェックする必要がありますか? しかし、次の errno ベースのエラー ハンドラはどうでしょうか。これはおそらく良い考えではありません。

realloc が非 NULL ポインターを返す場合、errno をゼロに設定する必要がありますか? これは解決策のようです。しかし...私はここを見ました: http://austingroupbugs.net/view.php?id=374。このリソースがどれほど権威があるかはわかりませんが、realloc に関しては、これについては非常に明確です。

「...標準では、文書化されていない限り、成功時にerrnoを検査できないことも明示されています...」

私の理解が正しければ、それは次のように言っています。つまり、errno をゼロにリセットできますか? 一度も見ずに?何が悪いのか、何が良いのかを理解して判断するのは非常に不明確だと思います。

そもそもなぜ realloc がこの errno を設定するのか、まだ理解できません。そして、「無効な引数」の値はどういう意味ですか? man ページには記載されておらず、errno ENOMEM (通常は 12 番) のみが記載されています。何か問題が発生する可能性がありますか?この単純なプログラムの何かが Mac OS X でこの動作を引き起こしているのでしょうか? おそらくそうでしょう...だから、2つの主な質問は次のとおりです。

  1. なにが問題ですか?と
  2. それを修正する方法は?より正確には、この単純なプログラムを改善して、Mac OS X の realloc が成功したときに errno をゼロのままにする方法は?
4

2 に答える 2

9

の目的を誤解していると思いますerrno-失敗した場合にのみ定義されます。POSIX 標準を引用するには:

errno の値は、関数の戻り値によって有効であることが示されている場合にのみ調べる必要があります。IEEE Std 1003.1-2001 のこのボリュームの関数は、 errno をゼロに設定してはなりません。

(私による大胆な顔)。errnoしたがって、呼び出しが成功した後に 0 になることは期待できません。戻り値のみを確認する必要があります。そうすれば、使用に失敗した理由を判断できますが、その逆は判断できerrnoません (つまり、 の値はエラー状態の存在を示してerrnoいませ。それはその目的ではありません。実際には、エラーがなければ触れません)。

于 2012-03-05T01:44:21.160 に答える
1

どうやら、realloc() の実装にバグがあるようです。eglibc の realloc のソース コード ( https://github.com/Xilinx/eglibc/blob/master/malloc/malloc.c#L2907 ) を少し読むと、メインの「アリーナ」からメモリを取得できない場合に問題が発生するようです。 "しかし、二次的なものから、errno 値をゼロにリセットしていないことがわかりました。そのファイルで errno 変数がゼロに設定されることはありません。

修正されるまでは、戻り値が null でない場合は errno==ENOMEM を無視することでおそらく安全です。

于 2015-12-03T15:17:44.360 に答える