この章の内容
親切なヒント
皆さんこんにちは、Cbiltpsです 私のブログでは、わかりにくい文章や言葉で表現しにくい要点などがある場合は、画像を載せています。なので、写真付きのブログはとても重要です!!!
私に興味があれば、私の最初のブログをご覧ください。
序章
今日書いたのは【C语言初阶】
ブログの最後のコンテンツ「実践的なデバッグスキル」【C语言进阶】
です。近々、関連コンテンツを書き始めます。
私のブログはここで書かれていますが、コードを書くこの段階で問題が発生する可能性が非常に高いです。
コードを書く過程で、文法はかなり理解できたように思えますが、書き方はわかっていても、書いた後にバグが多い可能性があります。コンパイルして実行することはできますが、実行した結果が期待どおりではありません。実行中 起動したプログラムは、さまざまな理由でクラッシュした可能性があります。
では、どうすれば問題を解決できるでしょうか?
それから、私のブログに来て学んでください。デバッグ スキルを使って問題を解決する方法を教えます。
もちろん、デバッグ能力には練習が必要であり、良い結果を得るには積極的に練習する必要があります。!!
この章の要点
- バグとは何ですか?
- デバッグとは何ですか? それはどれくらい重要ですか?
- デバッグとリリースの概要
- Windows 環境のデバッグの概要
- デバッグの例
- 良い (そしてデバッグしやすい) コードを書く方法
- よくあるプログラミングの間違い
本文の始まり
1. バグとは何ですか?
1945 年、小さな蛾がコンピューターの回路に侵入し、システムが正常に機能しなくなってしまいました。グレース・ヒーバーという名前の船長は作業日誌に蛾を叩き殺し、「今日の作業を完了できない原因はこのバグです」と書きました。これまでのところ、プログラマーとバグの間の 75 年にわたる愛憎関係が始まりました。
そういえば、次の人を紹介しましょう。
スタンダードな美しさ!
グレース・マレー・ホッパー(Grace Murray Hopper)は、1906年12月9日にアメリカのニューヨークで生まれた、コンピューターソフトウェア工学のファーストレディであり、アメリカ海軍の提督です。
このおばあちゃんは冷酷な人です。彼女の人生を一言で言い表すと、「素晴らしい」という言葉しかありません。!!彼女だよ、彼女だよ!
そしてこれがその日の彼女の作業記録です。
2. デバッグとは何ですか? それはどれくらい重要ですか?
「起こったことには必ず追跡すべき痕跡がなければなりません。明確な良心があれば、隠す必要はなく、兆候はありません。もし
良心があれば兆候が多ければ多いほど、ブドウの木をたどるのが簡単になります。そして、以上が推論の道です。このように下流に行くことは
犯罪であり、上流に行くことは真実です。
優れたプログラマーは優れた探偵です。
すべてのデバッグは、ケースを解決しようとするプロセスです。
2.1 デバッグとは何ですか?
こんな感じでコーディングしてるのかな?
こんな感じでデバッグしたんですか?
上記の状況が発生した場合は、迷信に基づいてデバッグを行っています。
この点に関しては断ります!
科学的なデバッグが必要です!
调试(Debugging / Debug)又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。
2.2 試運転の基本手順
发现
、承认
プログラムエラーの存在隔离
など消除
の形式でエラーを見つけます。确定
エラーの原因提出
エラーを修正するための解決策- プログラムエラーの場合
改正
、重新测试
2.3 デバッグとリリースの概要
-
デバッグは通常 と呼ばれ
调试版本(可以调试)
、デバッグ情報が含まれ、最適化は行われません。これはプログラマがプログラムをデバッグするのに便利です。 -
リリースは と呼ばれ
发布版本(不能调试)
、ユーザーが使いやすいようにプログラムのコードサイズや実行速度を最適化するためにさまざまな最適化を行うことがよくあります。
ここに質問があります:
あなたがテスターである場合、デバッグ バージョンをテストしますか、それともリリース バージョンをテストしますか? テスター
として、あなたは顧客の視点に立たなければなりません。それで、测试人员测试的是Release版本。
3. Windows環境でのデバッグ入門
3.1 デバッグ環境の準備
環境でオプションを選択することによってのみdebug
、コードを正常にデバッグできます。
3.2 ショートカット キーを学ぶ
最もよく使用されるショートカット キー:
-
F5
:デバッグを開始します。次のブレークポイントに直接ジャンプするためによく使用されます
。F5 を使用すると正確なデバッグができません。途中で停止しないと終了してしまうため、F5 は F9 と組み合わせて使用する必要があります。 -
F9
:ブレークポイントの作成とブレークポイントのキャンセル
ブレークポイントはプログラム内の任意の場所に設定できます。
このようにして、プログラムを任意の位置で停止し、ステップごとに実行できます。
3.2.1 条件付きブレークポイント
ループの n 番目のループにブレークポイントを設定したい場合、これで問題ありませんか? 答えは間違いなく「はい」です。
for (i = 0; i < 10; i++)
{
printf("%d\n", arr[i]);
}
上記のコードの i = 3 にブレークポイントを設定する場合 (以下のように操作します):
3.2.2 複数のブレークポイント
プログラムに複数のブレークポイントを設定しているときに F5 キーを押すとどうなりますか?
次の停電に入りたい場合は、まず前のブレークポイントをキャンセルしてから、F5 キーを押して、入りたいブレークポイントに入ります。
-
F10
:プロセスごと -
通常はプロセスを処理するために使用され、プロセスは関数呼び出しまたはステートメントになります。
-
F11
:ステートメントごとにステートメントを毎回実行しますが、このショートカット キーを使用すると、実行ロジックを関数に
入力できます(これが最も長く使用されます)。
-
CTRL + F5
:デバッグせずに実行を開始する デバッグ
せずにプログラムを直接実行したい場合は、これを直接使用できます。
VS2019 にはまだたくさんのショートカット キーがあります。CSDNでショートカット キーのブログを見つけました。ぜひご覧ください。
3.3 デバッグ中にプログラムの現在の情報を表示する
デバッグ モードでは、[デバッグ] をクリックしてウィンドウ ページに入ります。次のオプションがあり、すべてクリックできます。
3.3.1 一時変数の値を表示する
デバッグの開始後に変数の値を観察するために使用されます。
3.3.2 メモリ情報の表示
デバッグ開始後、メモリ情報を観察するために使用されます。
3.3.3 コールスタックの表示
コールスタックを通じて、関数の呼び出し関係と現在の呼び出しの位置を明確に反映できます。
3.3.4 アセンブリ情報の表示
分解とは何ですか?
逆アセンブリ (Disassembly): ターゲット コードをアセンブリ コードに変換するプロセス。機械語をアセンブリ言語コードに変換することとも言えます。低レベルから高レベルの意味で、ソフトウェア クラッキングでよく使用されます (たとえば、登録コードを解決したり、登録マシンを作成したりするために、どのように登録されているかを調べたり、プラグイン技術、ウイルス解析、リバース エンジニアリング、ソフトウェア ローカライゼーションなどの分野を研究します。
逆アセンブリ言語の学習と理解は、ソフトウェアのデバッグ、脆弱性分析、OS カーネルの原則、高級言語コードの理解に非常に役立ち、その過程でソフトウェア作成者のプログラミングのアイデアを理解することができます。
一言で言えば、ソフトウェアの謎めいた動作メカニズムはすべて逆アセンブリ コードの中にあります。
逆アセンブリに入るには 2 つの方法があります。
- 逆アセンブルに入る方法は次のとおりです。
- 別の方法: デバッグ モードで、右クリックして[逆アセンブリ] に移動します。
3.3.5 レジスタ情報の表示
現在の動作環境でのレジスタの使用状況情報を確認できます。
4. いくつかのデバッグ例
4.1 デバッグ例 1
実装コード:シーク1! +2!+3!...+n! (オーバーフローは考慮されません)
#include <stdio.h>
int main()
{
int n = 0;
int i = 0;
int ret = 1;
int sum = 0;
for (n = 1; n <= 3; n++)
{
for (i = 1; i <= n; i++)
{
ret *= i;
}
sum += ret;
}
printf("%d\n", sum);//打印后输出的竟然是15?
return 0;
}
この時点では、9 が出力されると予想されますが、実際の出力は 15 です?
デバッグを直接開始し、監視ウィンドウを開きます:
最後に、バグを変更します。
for (n = 1; n <= 3; n++)
{
ret = 1;//在这里把 ret 清为1就可以了
for (i = 1; i <= n; i++)
{
ret *= i;
}
sum += ret;
}
デバッグするときは、いくつかのことを行う必要があります。
- まず、問題の原因を推測します(問題の考えられる原因を事前に判断することが最善です)。
- 実際には、手動によるデバッグが必要です。
- デバッグするとき、私たちは良いアイデアを持っています(自分で書くコード、プログラムが実行される場所で何が起こるべきか、期待したことが起こらない場合はバグであることを知る必要があります)。
4.2 デバッグ例 2 (2016Nice 筆記試験問題)
以下のコードを実行して何が起こるか確認してください。
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hehe\n");//打印出来居然是无限循环?
}
return 0;
}
実行して無限ループであることがわかったら、デバッグを開始します。
この間に問題が見つかりました。i
と の値はarr[12]
同じで、アドレスも同じです。なぜですか?
したがって、i
それを範囲に入れるだけです。
for (i = 0; i <= 10; i++)//改成这样就可以了
ここで、前の知識ポイントを確認します。Debug和Release
リリース バージョンが最適化できることはわかったので、リリース バージョンに変更して何が起こるかを見てみましょう。
これはどのように行われるのでしょうか? 観察しやすいように、コードを次のように変更しました。
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", & i);
printf("&p\n", &arr[0]);
printf("&p\n", &arr[9]);
return 0;
}
このケースは本から来ています《C陷阱和缺陷》
、この本は非常に古典的です、あなたはそれを読むことができます!
この事例は2016Nice公司校招
筆記試験問題にも出題されました!
このような質問に遭遇した場合は、次の点に注意してください (3 つの点に注意してください)。
- スタック上のローカル変数
- スタック領域の使用習慣(上位アドレス→下位アドレス)を絵で説明できます
- 添字が範囲外になると、無限ループが発生する可能性があります
5. プログラミングにおけるよくあるエラー
- コンパイルエラー
- リンクエラー
- ランタイムエラー
5.1 コンパイルエラー
エラー メッセージを直接読んで (ダブルクリックして) 問題を解決するか、経験に頼って問題を解決するのは比較的簡単です。
#include <stdio.h>
int main()
{
printf("haha\n");
return 0 //注意这里没有加;
//这就是编译型错误:语法错误
}
5.2 リンクされたエラー
エラー メッセージを確認し、主にコード内のエラー メッセージ内の識別子を見つけて、問題の場所を特定します。
通常标识符名不存在
、 または拼写错误
。
#include <stdio.h>
int main()
{
int ret = Add(2, 3);//但是我并没有定义函数或者函数的名字写错也会链接错误
printf("%d\n", ret);
return 0;
}
5.3 実行時エラー
デバッグを利用して段階的に問題を特定するのは最も困難です。
上記の例は操作上のエラーであるため、ここではこれ以上の例は示しません。
6. 良い(デバッグしやすい)コードを書く方法
6.1 優れたコードのコーディングスキル
- コードは正常に動作します
- いくつかのバグ
- 効率的
- 高い可読性
- 高いメンテナンス性
- 明確にメモする
- 完全なドキュメント
一般的なコーディング スキル:
- アサートを使用します(特に以下のコードデモで)
assert(src != NULL);//这是一个宏,这个宏叫断言 —— 在Release版本中会被优化掉
assert(dest != NULL);
- constを使用してみます(特に次のコードのデモで)。
ここでそれについて話しましょう:const修饰指针
1.ポインタの左側にconst
配置されます。変更されるのは、ポインタが指すコンテンツです。ポインタが指す内容はポインタを介して変更できませんが、ポインタ変数自体は変更できます。*
(*p)
(p)
int n = 100;
const int* p = #// int const* p = #//在 * 的左边就可以
*p = 20;//err
p = &n;//ok
2.右側にconst
配置: ポインター変数自体が変更されます。ポインタ変数自体は変更できませんが、ポインタが指す内容は変更できます。*
(p)
(p)
(*p)
int n = 100;
int* const p = #
*p = 20;//ok
p = &n;//err
- 適切なコーディング スタイルを開発する
- 必要なコメントを追加する
- コーディングの落とし穴を回避する
6.2 古典的なケースのデモンストレーション
その前に、C 言語ライブラリ関数には次のstrcpy
関数があることは誰もが知っています。
//它是这样用的
#include <stdiuo.h>
#include <string.h>
int main()
{
//strcpy - string copy - 字符创拷贝
char arr1[] = "abcdef";//arr1里面有7个字符,最后还有个\0
char arr2[10] = {
0 };
strcpy(arr2, arr1);//arr1的字符全部拷贝到arr2
printf("%s\n", arr2);
return 0;
}
次に、関数をカスタマイズして実装をシミュレートします库函数strcpy
。
//dest 是指向目标空间的
//src 是指向源字符串的
//1 这段代码不太好,只是完成了功能而已
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//传'\0'
}
//2 简洁了一点
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest++ = *src++;
}
*dest = *src;
}
//3 这样写更简洁
void my_strcpy(char* dest, char* src)
{
//1. 拷贝字符
//2. 遇到'\0'循环停止,'\0'本质就是0
while (*dest++ = *src++)
{
;
}
}
//虽然上面的代码越来越简洁了,但是还是不好!
ポインタを使用するため、ポインタに出会ったときにそれを直接使用するという非常に危険な点があります空指针
。
それが null ポインターの場合、null ポインターは逆参照され、このコードはクラッシュ (ハング) します。
初歩的なポインタのブログで、ポインタを使用する前にポインタについて判断することが最善であると述べました。
次に、次のように関数を設計します。
//4 但是这种写法每次进入循环的时候都会判断,就会很麻烦
void my_strcpy(char* dest, char* src)
{
if (src == NULL || dest == NULL)
{
return;
}
while (*dest++ = *src++)
{
;
}
//5 但是这样还是有问题
#include <assert.h>
void my_strcpy(char* dest, char* src)
{
assert(src != NULL);//这是一个宏,这个宏叫断言 —— 在Release版本中会被优化掉
assert(dest != NULL);
while (*dest++ = *src++)//如果这个条件不小心写反怎么办?
{
;
}
//6 这个版本如果看不懂,请看下面的图解
void my_strcpy(char* dest, const char* src)//const让*src不被修改
//const放在这里,对于代码的健壮性(或者鲁棒性)增加了。
{
assert(src != NULL);
assert(dest != NULL);
while (*dest++ = *src++)
{
;
}
}
//7 经过了7次的优化,这个是最终版!
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(src != NULL);
assert(dest != NULL);
char* temporary = dest;
while (*dest++ = *src++)
{
;
}
return temporary;
}
int main()
{
char arr1[] = "abcdef";
char arr2[10]= "xxxxxxxxx";
char* ret = my_strcpy(arr2, arr1);
printf("%s\n", ret);
return 0;
}
この関数を作成するときは、罠に陥らないようにいくつかの点に注意してください。
- ソース文字列には次のものが必要です
\0
char arr1[] = {
'a', 'b', 'c' };//这里没有'\0',在拷贝的时候会越界直到找到'\0'
char arr2[10] = "xxxxxxxx";
- ターゲットスペースは十分な大きさである必要があります
char arr1[] = "abcdef";//7个字符无法拷贝到三个字符的数组中
char arr2[3] = {
0 };
- ターゲット空間は変更可能である必要があります
char arr1[] = "abcdef";
const char* arr2 = "xxxxxxxxxxx";//目标空间没法修改,无法拷贝
//注意:这里的指针指向的是常量字符串,常量字符串是放在常量区的,不能修改。
//而且,这里最好用const来修饰,这样严谨的写代码安全性会提高。
//另外讲一个知识点:怎么把常量字符串放到了指针里?
//就是把常量字符串首元素地址存放在 arr2 中。
このケースは、書籍から来ています。《高质量C/C++编程》
書籍の最後の章にあるstrcpy シミュレーションの実装に関するトピックです。
7. 最後の注意事項
将来的にコードが不足するのは間違いです。
- よく考えてください- 問題がどこにあるのか考えてください
- デバッグ- 問題がどこにあるのか特定できないため、デバッグします。
- 質問する—— デバッグが解決しない場合は、他の人に質問してください
皆さんへの警告の言葉:
- デバッグスキルに熟練している必要があります。
進歩するために、多くの実践的なデバッグを試みてください。 - 初心者は、時間の 80% をコードの作成に費やし、20% をデバッグに費やすことがあります。
ただし、プログラマーは時間の 20% をプログラムの作成に費やしますが、時間の 80% はデバッグに費やします。 - 私たちは皆、簡単なデバッグについて話しています。
将来的には、マルチスレッド プログラムのデバッグなど、非常に複雑なデバッグ シナリオが登場する可能性があります。 - より多くのショートカット キーを使用して効率を向上させます。
全文終わり(ありがとう)
【C语言初阶】
映画全体は終わりました。私のフォローアップ作品【C语言进阶】
の関連内容にご注意ください。
私のブログを読んでここまで学習した方は、C 言語の基礎ができたと思います。
ここの先生に感謝します!
私のファンの皆さんに感謝します!
3回も応援してくださった皆様、
本当にありがとうございました! 皆様のご支援が私の継続的なモチベーションです!