2次元配列のエイリアスを作成するときのstrlenの予期しない最適化


28

これが私のコードです:

#include <string.h>
#include <stdio.h>

typedef char BUF[8];

typedef struct
{
    BUF b[23];
} S;

S s;

int main()
{
    int n;

    memcpy(&s, "1234567812345678", 17);

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

    n = strlen((char *)&s) / sizeof(BUF);
    printf("%d\n", n);
}

gcc 8.3.0または8.2.1を最適化レベル以外で使用すると-O00 2期待していたとおりに出力されます2 2。コンパイラは、strlenがに制限されb[0]ているため、除算される値と同じかそれ以上になることはないと判断しました。

これは私のコードのバグですか、コンパイラのバグですか?

これは明確に規格に明記されていませんが、ポインターの来歴の主流の解釈は、どのオブジェクトXでも、コード(char *)&Xは全体を反復できるポインターを生成するべきだと思いましたX-この概念はXたまたま持っていても成り立つはずです内部構造としてのサブ配列。

(ボーナス質問、この特定の最適化をオフにするgccフラグはありますか?)



4
参照:私のgcc 7.4.0は2 2さまざまなオプションでレポートします。
chux-モニカを

2
@Ale標準では、それらが同じアドレスにあることを保証しています(構造体に最初のパディングを含めることはできません)
MM

3
@ DavidRankin-ReinstateMonicaは、「char(*)[8]の境界がb [0]に制限されることになります。しかし、それは私が得る限りのことです」とそれは釘付けだと思います。以降s.bに限定されるb[0]ヌル文字はここで、ある場合には(1)アウトオブバウンドアクセス8非ヌル文字がある、UBである、(2)それは8つの文字に限定されず、従って二つのオプションていますlenは8未満なので、8で割るとゼロになります。したがって、(1)+(2)コンパイラを
組み合わせる

3
&s ==&s.bの場合、結果が異なる可能性はありません。@ user2162550が示すように、コンパイラがそれを認識できないgodbolt.org/z/dMcrdyの場合でも、strlen()は呼び出されず、コンパイラはその結果が何であるかを推測します。 コンパイラのバグです。
Ale

回答:


-1

私が見ることができるいくつかの問題があり、それらはコンパイラがメモリのレイアウトを決定する方法によって影響を受ける可能性があります。

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

上記のコードs.bは、8文字の配列の23エントリ配列です。単に参照するs.bと、23バイト配列の最初のエントリ(および8文字配列の最初のバイト)のアドレスが取得されます。コードが言うとき&s.b、これは配列のアドレスのアドレスを要求しています。内部では、コンパイラはローカルストレージを生成し、そこに配列のアドレスを格納し、ローカルストレージのアドレスをstrlen

あなたは2つの可能な解決策を持っています。彼らです:

    n = strlen((char *)s.b) / sizeof(BUF);
    printf("%d\n", n);

または

    n = strlen((char *)&s.b[0]) / sizeof(BUF);
    printf("%d\n", n);

私もあなたのプログラムを実行して問題を実証しようとしましたが、clangと-Oオプションのgccのバージョンの両方がまだ期待どおりに機能しました。それだけの価値があるので、私はx86_64-pc-linux-gnuでclangバージョン9.0.0-2およびgccバージョン9.2.1を実行しています。


-2

コードにエラーがあります。

 memcpy(&s, "1234567812345678", 17);

たとえば、sがbで始まるとしても、は危険です:

 memcpy(&s.b, "1234567812345678", 17);

2番目のstrlen()にもエラーがあります

n = strlen((char *)&s) / sizeof(BUF);

たとえば、次のようになります。

n = strlen((char *)&s.b) / sizeof(BUF);

文字列sbは、正しくコピーされた場合、17文字の長さが必要です。構造体が整列されている場合、構造体がどのようにメモリに格納されているかわかりません。sbに実際にコピーされた17文字が含まれていることを確認しましたか?

したがって、strlen(sb)は17を表示する必要があります

%dは整数であり、変数nは整数として宣言されているため、printfは整数のみを表示します。sizeof(BUF)、8にする必要があります

したがって、17を8で割ると(17/8)、nは整数として宣言されるため、2が出力されます。memcpyはsbではなくsにデータをコピーするために使用されていたので、これはメモリアライメントに関係しているためだと思います。それが64ビットコンピュータであると仮定すると、1つのメモリアドレスに8文字が存在する可能性があります。

たとえば、誰かがmalloc(1)を呼び出したとしましょう。次の「空きスペース」は整列されていません...

文字列のコピーがsbではなくs構造体に行われたため、2番目のstrlen呼び出しは正しい番号を示しています

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.