0
2
実現したいこと
プログラムコードが同一なら、表示結果も同じになるはず。
結果に違いが出るその理由を知りたい。
前提
C言語で1から13までのランダムな数字を、バブルソートで小さい順に並び替えるプログラムを2種のPCで実行したところ表示結果に違いが現れました。
発生している問題・エラーメッセージ
エラーではないのですが、表示結果に違いが現れます。 下記全く同じコードを2種類のPCで実行した際の結果が以下のように異なりました。 《表示結果》 LaVie: 0 1 2 3 4 5 6 7 8 9 10 11 12 VAIO : 1 2 3 4 5 6 7 8 9 10 11 12 13 ← 望まれる結果
該当のソースコード
C言語 ソースコード #include <stdio.h> int main(void) { int a[13] = {13, 1, 5, 4, 3, 7, 9, 10, 8, 6, 11, 2, 12}; int i, j, k, tmp; puts("並べ替え前:"); for(i = 0; i < 13; i++) { printf("%d ", a[i]); } putchar('\n'); for(i = 13; i > 0; i--) { // バブルソート for(j = 0; j < i; j++) { if(a[j] > a[j+1]) { tmp = a[j]; a[j] = a[j+1]; a[j+1] = tmp; } for(k = 0; k < 13; k++) { // 途中経過表示 printf("%d ", a[k]); } putchar('\n'); } } puts("並べ替え後:"); for(i = 0; i < 13; i++) { printf("%d ", a[i]); } putchar('\n'); return 0; } ### 試したこと 上記全く同じコードを2種類のPCで実行した際の結果が以下のように異なりました。 《表示結果》 LAVI : 0 1 2 3 4 5 6 7 8 9 10 11 12 VAIO : 1 2 3 4 5 6 7 8 9 10 11 12 13 各々のPCに搭載されているCPUによって結果が変わったりするのでしょうか? コードにミスはありません。2機種とも何度も同一コードで実施しました。 他に考えられる要因があるとすれば何でしょうか?ご教示ください。 ### 補足情報(FW/ツールのバージョンなど) PC二機種のスペックは下記のとおりです。 1.NEC LaVie CPU: Core i5-3317U. CPUクロック, 1.7GHz OSは当初windows8.0 → 8.1 → 現状はWindows10へアップグレード 2.VAIO CPU: Core i7-1065G7 1.30GHz OSは当初windows10から、現状はwindow11へアップグレード
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答9件
#1
総合スコア10130
投稿2024/04/05 04:38
C
1 for(i = 13; i > 0; i--) { // バブルソート
上記で、13番目~1番目の要素を取得していますが、配列aは13個なので0番目~12番目しかなく、13番目がどこにも存在しません。
こうなった場合(存在しない)13番目が何になるかは、決まっていません。
LaVieの場合は13番目がたまたま0になっていて、それがソートに反映されたのでしょう。
#2
総合スコア13168
投稿2024/04/05 04:50
編集2024/04/05 05:16とりあえずコードのマークダウンはコード(やパソコンのコンソールの表示)だけに適用するようにしたほうが良いと思います。
説明まで含まれても閉じられていて気が付かない場合があります。
一部で正常に動作した(ように見えた)からと、 CPU の所為かもと判断するには早すぎたようです。
for(i = 13; i > 0; i--) { // バブルソート for(j = 0; j < i; j++) { if(a[j] > a[j+1]) {
は i=13 で j=12 (j<i) の時、 a[j+1] → a[13] を参照します。つまり
コードにミスはありません。
という判断が要因です。
a を 14 個まで拡張して a[13] にも値を入れ、その値がソート されない ことを確認しては如何でしょうか。
c
1#include <stdio.h> 2 3int main(void) 4{ 5 int a[14] = {13, 1, 5, 4, 3, 7, 9, 10, 8, 6, 11, 2, 12, -1}; //-1を追加 6 int i, j, k, tmp; 7 8 puts("並べ替え前:"); 9 for(i = 0; i < 14; i++) { //13→14 10 printf("%d ", a[i]); 11 } 12 putchar('\n'); 13 14 for(i = 13; i > 0; i--) { // バブルソート 15 for(j = 0; j < i; j++) { 16 if(a[j] > a[j+1]) { 17 tmp = a[j]; 18 a[j] = a[j+1]; 19 a[j+1] = tmp; 20 } 21 for(k = 0; k < 14; k++) { // 途中経過表示 13→14 22 printf("%d ", a[k]); 23 } 24 putchar('\n'); 25 } 26 } 27 28 puts("並べ替え後:"); 29 for(i = 0; i < 14; i++) { //13→14 30 printf("%d ", a[i]); 31 } 32 putchar('\n'); 33 34 return 0; 35}
実行結果
並べ替え前: 13 1 5 4 3 7 9 10 8 6 11 2 12 -1 1 13 5 4 3 7 9 10 8 6 11 2 12 -1 1 5 13 4 3 7 9 10 8 6 11 2 12 -1 1 5 4 13 3 7 9 10 8 6 11 2 12 -1 1 5 4 3 13 7 9 10 8 6 11 2 12 -1 1 5 4 3 7 13 9 10 8 6 11 2 12 -1 1 5 4 3 7 9 13 10 8 6 11 2 12 -1 1 5 4 3 7 9 10 13 8 6 11 2 12 -1 1 5 4 3 7 9 10 8 13 6 11 2 12 -1 1 5 4 3 7 9 10 8 6 13 11 2 12 -1 1 5 4 3 7 9 10 8 6 11 13 2 12 -1 1 5 4 3 7 9 10 8 6 11 2 13 12 -1 1 5 4 3 7 9 10 8 6 11 2 12 13 -1 1 5 4 3 7 9 10 8 6 11 2 12 -1 13 1 5 4 3 7 9 10 8 6 11 2 12 -1 13 1 4 5 3 7 9 10 8 6 11 2 12 -1 13 1 4 3 5 7 9 10 8 6 11 2 12 -1 13 1 4 3 5 7 9 10 8 6 11 2 12 -1 13 1 4 3 5 7 9 10 8 6 11 2 12 -1 13 1 4 3 5 7 9 10 8 6 11 2 12 -1 13 1 4 3 5 7 9 8 10 6 11 2 12 -1 13 1 4 3 5 7 9 8 6 10 11 2 12 -1 13 1 4 3 5 7 9 8 6 10 11 2 12 -1 13 1 4 3 5 7 9 8 6 10 2 11 12 -1 13 1 4 3 5 7 9 8 6 10 2 11 12 -1 13 1 4 3 5 7 9 8 6 10 2 11 -1 12 13 1 4 3 5 7 9 8 6 10 2 11 -1 12 13 1 3 4 5 7 9 8 6 10 2 11 -1 12 13 1 3 4 5 7 9 8 6 10 2 11 -1 12 13 1 3 4 5 7 9 8 6 10 2 11 -1 12 13 1 3 4 5 7 9 8 6 10 2 11 -1 12 13 1 3 4 5 7 8 9 6 10 2 11 -1 12 13 1 3 4 5 7 8 6 9 10 2 11 -1 12 13 1 3 4 5 7 8 6 9 10 2 11 -1 12 13 1 3 4 5 7 8 6 9 2 10 11 -1 12 13 1 3 4 5 7 8 6 9 2 10 11 -1 12 13 1 3 4 5 7 8 6 9 2 10 -1 11 12 13 1 3 4 5 7 8 6 9 2 10 -1 11 12 13 1 3 4 5 7 8 6 9 2 10 -1 11 12 13 1 3 4 5 7 8 6 9 2 10 -1 11 12 13 1 3 4 5 7 8 6 9 2 10 -1 11 12 13 1 3 4 5 7 8 6 9 2 10 -1 11 12 13 1 3 4 5 7 6 8 9 2 10 -1 11 12 13 1 3 4 5 7 6 8 9 2 10 -1 11 12 13 1 3 4 5 7 6 8 2 9 10 -1 11 12 13 1 3 4 5 7 6 8 2 9 10 -1 11 12 13 1 3 4 5 7 6 8 2 9 -1 10 11 12 13 1 3 4 5 7 6 8 2 9 -1 10 11 12 13 1 3 4 5 7 6 8 2 9 -1 10 11 12 13 1 3 4 5 7 6 8 2 9 -1 10 11 12 13 1 3 4 5 7 6 8 2 9 -1 10 11 12 13 1 3 4 5 6 7 8 2 9 -1 10 11 12 13 1 3 4 5 6 7 8 2 9 -1 10 11 12 13 1 3 4 5 6 7 2 8 9 -1 10 11 12 13 1 3 4 5 6 7 2 8 9 -1 10 11 12 13 1 3 4 5 6 7 2 8 -1 9 10 11 12 13 1 3 4 5 6 7 2 8 -1 9 10 11 12 13 1 3 4 5 6 7 2 8 -1 9 10 11 12 13 1 3 4 5 6 7 2 8 -1 9 10 11 12 13 1 3 4 5 6 7 2 8 -1 9 10 11 12 13 1 3 4 5 6 7 2 8 -1 9 10 11 12 13 1 3 4 5 6 2 7 8 -1 9 10 11 12 13 1 3 4 5 6 2 7 8 -1 9 10 11 12 13 1 3 4 5 6 2 7 -1 8 9 10 11 12 13 1 3 4 5 6 2 7 -1 8 9 10 11 12 13 1 3 4 5 6 2 7 -1 8 9 10 11 12 13 1 3 4 5 6 2 7 -1 8 9 10 11 12 13 1 3 4 5 6 2 7 -1 8 9 10 11 12 13 1 3 4 5 2 6 7 -1 8 9 10 11 12 13 1 3 4 5 2 6 7 -1 8 9 10 11 12 13 1 3 4 5 2 6 -1 7 8 9 10 11 12 13 1 3 4 5 2 6 -1 7 8 9 10 11 12 13 1 3 4 5 2 6 -1 7 8 9 10 11 12 13 1 3 4 5 2 6 -1 7 8 9 10 11 12 13 1 3 4 2 5 6 -1 7 8 9 10 11 12 13 1 3 4 2 5 6 -1 7 8 9 10 11 12 13 1 3 4 2 5 -1 6 7 8 9 10 11 12 13 1 3 4 2 5 -1 6 7 8 9 10 11 12 13 1 3 4 2 5 -1 6 7 8 9 10 11 12 13 1 3 2 4 5 -1 6 7 8 9 10 11 12 13 1 3 2 4 5 -1 6 7 8 9 10 11 12 13 1 3 2 4 -1 5 6 7 8 9 10 11 12 13 1 3 2 4 -1 5 6 7 8 9 10 11 12 13 1 2 3 4 -1 5 6 7 8 9 10 11 12 13 1 2 3 4 -1 5 6 7 8 9 10 11 12 13 1 2 3 -1 4 5 6 7 8 9 10 11 12 13 1 2 3 -1 4 5 6 7 8 9 10 11 12 13 1 2 3 -1 4 5 6 7 8 9 10 11 12 13 1 2 -1 3 4 5 6 7 8 9 10 11 12 13 1 2 -1 3 4 5 6 7 8 9 10 11 12 13 1 -1 2 3 4 5 6 7 8 9 10 11 12 13 -1 1 2 3 4 5 6 7 8 9 10 11 12 13 並べ替え後: -1 1 2 3 4 5 6 7 8 9 10 11 12 13
-1 がソートされてしまっています。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
#3
総合スコア5675
投稿2024/04/05 06:32
この場合に配列の範囲外にアクセスしたことが原因であるのは他の方が提示している通りですが、 C の言語仕様として関連する部分を箇条書きで列挙するとこんな感じです。
a[j]
は*(a+j)
と同じ意味である (つまりj
の内容が13
なら*(a+13)
ということ)- ポインタに対する加算の結果は配列の要素、または配列の最後の要素のひとつ後ろを指すポインタにならなければならない (
a+13
はこの条件を満たすのでここまでなら問題はない) - ポインタが配列の最後の要素のひとつ後ろを指しているときにそのポインタを単項
*
のオペランドとしてはならない (禁止事項にひっかかっている) - 言語仕様で「してはならない」としている要求を守っていないプログラムの動作は未定義である
- 未定義とはその状況を無視して予測不可能な結果を返しても良い (デタラメな動作やクラッシュなども含む)
実際の動作としてはプログラマが使うために確保した領域ではない部分へのアクセスが起こったときにそれを検出したりはしないのが普通です。 デタラメな値が入っているかもしれませんし、他の用途で使っている場所なのかもしれませんし、その場所に実メモリが割り当てられていないことすらあるかもしれません。 やった結果がどうなるのかは言語仕様として規定しませんのでどんなことも起こります。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
#4
総合スコア85778
投稿2024/04/05 10:01
回答はすでにある通りで、a[13]の位置のメモリーにたまたま何が入っていたかにより、
(ケース1) データのどれよりも大きな値が入っていれば、見かけ上影響しない(ソートで移動しないので)
(ケース2) データの最大値より小さい値が入っていれば、ソートしている内にデータの最大値と入れ替わって、データに混ざってくる
配列の範囲外のメモリーにアクセスした場合の挙動は、CPUの性能には無関係で、
OSの違いや、コンパイラの違い、使われるライブラリの違いなどで異なります。
今回はおそらく未使用部分ですが、i や j の変数と隣接していた場合は、範囲外への代入でiやjが変化して理解困難な挙動になるでしょうね。
C
1 int i,a[1],j; 2 i=0; 3 j=0; 4 a[1]=10; 5 printf("%d %d\n",i,j);
アドバイスとしては、
コードにミスはありません。
この言明は主観ですが、客観的な事実としては「ある環境で、あるデータに対して想定した結果となった」というだけでしょうね。その程度の事実では「コードにミスはありません」とかは言えません。上級者が絶対に言わない言葉だと思います。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
#5
総合スコア2150
投稿2024/04/05 12:57
本当に回答者の方々のおっしゃる通りなのですが,
問題があるかどうかをどうやって見つけるか?ということも重要かと思います。
こちらで cppcheck
を通しても見つからなかったので,
具体的にどうやって見つけるかの方法をぐぐったところ,
gcc, clangの場合は -fsanitize=address
オプションでコンパイルすると
メモリの不正アクセスがあると表示してくれるようです。
具体的な実行例は次の通りです。
sh
1ujimushi@ubuntu-23.10:~/test$ gcc -fsanitize=address -o test20240405 ./test20240405.c 2ujimushi@ubuntu-23.10:~/test$ ./test20240405 3並べ替え前: 413 1 5 4 3 7 9 10 8 6 11 2 12 51 13 5 4 3 7 9 10 8 6 11 2 12 61 5 13 4 3 7 9 10 8 6 11 2 12 71 5 4 13 3 7 9 10 8 6 11 2 12 81 5 4 3 13 7 9 10 8 6 11 2 12 91 5 4 3 7 13 9 10 8 6 11 2 12 101 5 4 3 7 9 13 10 8 6 11 2 12 111 5 4 3 7 9 10 13 8 6 11 2 12 121 5 4 3 7 9 10 8 13 6 11 2 12 131 5 4 3 7 9 10 8 6 13 11 2 12 141 5 4 3 7 9 10 8 6 11 13 2 12 151 5 4 3 7 9 10 8 6 11 2 13 12 161 5 4 3 7 9 10 8 6 11 2 12 13 17================================================================= 18==68968==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x76f65df00054 at pc 0x565fcf74179f bp 0x7ffd0bb24c10 sp 0x7ffd0bb24c00 19READ of size 4 at 0x76f65df00054 thread T0 20 #0 0x565fcf74179e in main (/home/ujimushi/test/test20240405+0x179e) (BuildId: c07d64b62100eac3ef1552ab3462c050c694a5f7) 21 #1 0x76f65fe2814f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 22 #2 0x76f65fe28208 in __libc_start_main_impl ../csu/libc-start.c:360 23 #3 0x565fcf7411c4 in _start (/home/ujimushi/test/test20240405+0x11c4) (BuildId: c07d64b62100eac3ef1552ab3462c050c694a5f7) 24 25Address 0x76f65df00054 is located in stack of thread T0 at offset 84 in frame 26 #0 0x565fcf741298 in main (/home/ujimushi/test/test20240405+0x1298) (BuildId: c07d64b62100eac3ef1552ab3462c050c694a5f7) 27 28 This frame has 1 object(s): 29 [32, 84) 'a' (line 5) <== Memory access at offset 84 overflows this variable 30HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork 31 (longjmp and C++ exceptions *are* supported) 32SUMMARY: AddressSanitizer: stack-buffer-overflow (/home/ujimushi/test/test20240405+0x179e) (BuildId: c07d64b62100eac3ef1552ab3462c050c694a5f7) in main 33Shadow bytes around the buggy address: 34 0x76f65deffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 35 0x76f65deffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 36 0x76f65deffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 37 0x76f65defff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 38 0x76f65defff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 39=>0x76f65df00000: f1 f1 f1 f1 00 00 00 00 00 00[04]f3 f3 f3 f3 f3 40 0x76f65df00080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 0x76f65df00100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 42 0x76f65df00180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 43 0x76f65df00200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 44 0x76f65df00280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 45Shadow byte legend (one shadow byte represents 8 application bytes): 46 Addressable: 00 47 Partially addressable: 01 02 03 04 05 06 07 48 Heap left redzone: fa 49 Freed heap region: fd 50 Stack left redzone: f1 51 Stack mid redzone: f2 52 Stack right redzone: f3 53 Stack after return: f5 54 Stack use after scope: f8 55 Global redzone: f9 56 Global init order: f6 57 Poisoned by user: f7 58 Container overflow: fc 59 Array cookie: ac 60 Intra object redzone: bb 61 ASan internal: fe 62 Left alloca redzone: ca 63 Right alloca redzone: cb 64==68968==ABORTING 65ujimushi@ubuntu-23.10:~/test$
ちょうど回答者から指摘のあったタイミングでstack-buffer-overflow
が発生していることが分かります。
[32, 84) 'a' (line 5) <== Memory access at offset 84 overflows this variable
のログでa
の配列で発生したことも分かります。
また,Visual StudioのC++にも AddressSanitizer という機能があるようなので,
Windowsでも同様のことができるのではないでしょうか?
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
#6
総合スコア85778
投稿2024/04/06 00:34
gcc, clangの場合は -fsanitize=address オプションでコンパイルすると
今回だと
各々のPCに搭載されているCPUによって結果が変わったりするのでしょうか?
コードにミスはありません。
という変な思い込みを持たず、「配列の添え字範囲オーバー」を疑った段階で、間違い箇所はほとんど自明なので、実際にこの機能を使うまでも無い気もします。まあ、つかっても良いのですが、「このオプションを知らなかったので間違いを発見できなかった」訳ではなくて「添え字オーバーだと思わなかったから発見できなかった」訳ですよね。
状況を、「出力結果に元の13個のデータに無かった値が混入している」と正しく把握できるかどうかが鍵だったと思います。「混入」→「隣までソート範囲」→「添え字オーバー」→「ここだ!」は一本道です。
混入値が0だった事が、正しい状況把握を邪魔したのでしょうね。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
#7
総合スコア42
投稿2024/04/10 13:16
「コードにミスはありません」というのは、何か元のソースを見てその通り入力した、ということでしょうか。
そうであれば、データの数を変更してみるとか、いろいろ改造して自分のものにしてください。
また、元のソースが正しいとは限りません。大元があって、それを参考に自分流にアレンジして、とやっていく内にミスが入り込んでしまうのです。
for(i = 13; i > 0; i--) { // バブルソート
の部分は、
for(i = 13; i > 1; i--)
のアレンジミスではないかと思います。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
#8
総合スコア10130
投稿2024/04/11 02:30
for(i = 13; i > 0; i--) { // バブルソート
の部分は、
for(i = 13; i > 1; i--)
のアレンジミスではないかと思います。
それを言うなら、
C
1for(i = 12; i > 0; i--)
が正しいです。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。