0

私は仕事仲間に、定数修飾変数の値を本当に変更したい (そしてその方法を知っている) 場合は、いくつかのトリックを使用して変更できることを実証しようとしていましたが、デモ中に 2 つの「フレーバー」が存在することを発見しました定数値:何をしても変更できないものと、汚いトリックを使用して変更できるもの。

コンパイラがスタックに格納された値の代わりにリテラル値を使用する場合、定数値は変更できません(こちらを参照) 。

// TEST 1
#define LOG(index, cv, ncv) std::cout \
    << std::dec << index << ".- Address = " \
    << std::hex << &cv << "\tValue = " << cv << '\n' \
    << std::dec << index << ".- Address = " \
    << std::hex << &ncv << "\tValue = " << ncv << '\n'

const unsigned int const_value = 0xcafe01e;

// Try with no-const reference
unsigned int &no_const_ref = const_cast<unsigned int &>(const_value);
no_const_ref = 0xfabada;
LOG(1, const_value, no_const_ref);

// Try with no-const pointer
unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value);
*no_const_ptr = 0xb0bada;
LOG(2, const_value, (*no_const_ptr));

// Try with c-style cast
no_const_ptr = (unsigned int *)&const_value;
*no_const_ptr = 0xdeda1;
LOG(3, const_value, (*no_const_ptr));

// Try with memcpy
unsigned int brute_force = 0xba51c;
std::memcpy(no_const_ptr, &brute_force, sizeof(const_value));
LOG(4, const_value, (*no_const_ptr));

// Try with union
union bad_idea
{
    const unsigned int *const_ptr;
    unsigned int *no_const_ptr;
} u;

u.const_ptr = &const_value;
*u.no_const_ptr = 0xbeb1da;
LOG(5, const_value, (*u.no_const_ptr));

これにより、次の出力が生成されます。

1.- Address = 0xbfffbe2c    Value = cafe01e
1.- Address = 0xbfffbe2c    Value = fabada
2.- Address = 0xbfffbe2c    Value = cafe01e
2.- Address = 0xbfffbe2c    Value = b0bada
3.- Address = 0xbfffbe2c    Value = cafe01e
3.- Address = 0xbfffbe2c    Value = deda1
4.- Address = 0xbfffbe2c    Value = cafe01e
4.- Address = 0xbfffbe2c    Value = ba51c
5.- Address = 0xbfffbe2c    Value = cafe01e
5.- Address = 0xbfffbe2c    Value = beb1da

私はUB(constデータの値を変更する)に依存しているので、プログラムが奇妙な動作をすることが予想されます。しかし、この奇妙さは私が予想していた以上のものです。

コンパイラがリテラル値を使用していると仮定すると、コードが定数の値を変更する命令に到達すると (参照、ポインター、またはmemcpying によって)、値がリテラルである (未定義である) 限り、単に順序を無視します。ただし、動作)。これは、値が変更されない理由を説明していますが、次のとおりです。

  • 両方の変数のメモリアドレスが同じなのに、含まれる値が異なるのはなぜですか?

私の知る限り、同じメモリアドレスが異なる値を指すことはできないため、出力の1つが嘘をついています:

  • 本当に何が起こっているのですか?偽のメモリアドレスはどれですか (もしあれば)?

上記のコードにいくつかの変更を加えると、リテラル値の使用を避けることができるため、トリックが機能します (ソースはこちら)。

// TEST 2
// Try with no-const reference
void change_with_no_const_ref(const unsigned int &const_value)
{
    unsigned int &no_const_ref = const_cast<unsigned int &>(const_value);
    no_const_ref = 0xfabada;
    LOG(1, const_value, no_const_ref);    
}

// Try with no-const pointer
void change_with_no_const_ptr(const unsigned int &const_value)
{
    unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value);
    *no_const_ptr = 0xb0bada;
    LOG(2, const_value, (*no_const_ptr));
}

// Try with c-style cast
void change_with_cstyle_cast(const unsigned int &const_value)
{
    unsigned int *no_const_ptr = (unsigned int *)&const_value;
    *no_const_ptr = 0xdeda1;
    LOG(3, const_value, (*no_const_ptr));
}

// Try with memcpy
void change_with_memcpy(const unsigned int &const_value)
{
    unsigned int *no_const_ptr = const_cast<unsigned int *>(&const_value);
    unsigned int brute_force = 0xba51c;
    std::memcpy(no_const_ptr, &brute_force, sizeof(const_value));
    LOG(4, const_value, (*no_const_ptr));
}

void change_with_union(const unsigned int &const_value)
{
    // Try with union
    union bad_idea
    {
        const unsigned int *const_ptr;
        unsigned int *no_const_ptr;
    } u;

    u.const_ptr = &const_value;
    *u.no_const_ptr = 0xbeb1da;
    LOG(5, const_value, (*u.no_const_ptr));
}

int main(int argc, char **argv)
{
    unsigned int value = 0xcafe01e;
    change_with_no_const_ref(value);
    change_with_no_const_ptr(value);
    change_with_cstyle_cast(value);
    change_with_memcpy(value);
    change_with_union(value);

    return 0;
}

次の出力が生成されます。

1.- Address = 0xbff0f5dc    Value = fabada
1.- Address = 0xbff0f5dc    Value = fabada
2.- Address = 0xbff0f5dc    Value = b0bada
2.- Address = 0xbff0f5dc    Value = b0bada
3.- Address = 0xbff0f5dc    Value = deda1
3.- Address = 0xbff0f5dc    Value = deda1
4.- Address = 0xbff0f5dc    Value = ba51c
4.- Address = 0xbff0f5dc    Value = ba51c
5.- Address = 0xbff0f5dc    Value = beb1da
5.- Address = 0xbff0f5dc    Value = beb1da

ご覧のとおり、const 修飾された変数はchange_with_*呼び出しごとに変更され、動作はこの事実を除いて以前と同じであるため、const データが使用されたときにメモリ アドレスの奇妙な動作が現れると仮定したくなりました。値ではなくリテラルとして。

したがって、この仮定を確実にするために、unsigned int valueinmainをに変更して最後のテストを行いましたconst unsigned int value

// TEST 3
const unsigned int value = 0xcafe01e;
change_with_no_const_ref(value);
change_with_no_const_ptr(value);
change_with_cstyle_cast(value);
change_with_memcpy(value);
change_with_union(value);

TEST 2驚いたことに、出力は( code here )と同じであるため、データはパラメーターとして使用されているため、リテラル値ではなく変数として渡されていると思われるため、次のように考えています。

  • コンパイラが const 値をリテラル値として最適化することを決定する要因は何ですか?

簡単に言えば、私の質問は次のとおりです。

  • TEST 1
    • const値とno-const値が同じメモリアドレスを共有しているのに、含まれる値が異なるのはなぜですか?
    • この出力を生成するためにプログラムに続く手順は何ですか? 偽のメモリアドレスはどれですか (もしあれば)?
  • TEST 3
    • コンパイラが const 値をリテラル値として最適化することを決定する要因は何ですか?
4

1 に答える 1

2

一般に、未定義の動作を分析しても意味がありません。分析の結果を別のプログラムに転送できるという保証がないからです。

この場合、コンパイラが定数伝播と呼ばれる最適化手法を適用したと仮定すると、動作を説明できます。その手法では、コンパイラが値を認識しているconst変数の値を使用すると、コンパイラは変数の使用をそのconst変数の値に置き換えます (コンパイル時に認識されるため)。アドレスの取得など、変数のその他の用途は置き換えられません。

この最適化は有効です。これは、定義された変数を変更すると未定義の動作が発生し、コンパイラはプログラムが未定義の動作を呼び出さないconst想定できるためです。

したがって、 では、TEST 1すべて同じ変数であるため、アドレスは同じですが、各ペアの最初の値はコンパイラーが変数の値であると (正しく) 推定するものを反映し、2 番目の値は実際の値を反映するため、値は異なります。そこに保管されます。TEST 2およびではTEST 3、関数の引数が定数値を参照することをコンパイラが 100% 確信できないため、コンパイラは最適化を行うことができません (および ではTEST 2、そうではありません)。

于 2013-05-21T13:59:30.823 に答える