1. ホーム
  2. リナックス

C言語改善

2022-02-10 19:24:19
<パス

*++p, *(++P) : アドレスがインクリメントされてから内容が取得されます。
*p++, *(P++) : 最初にコンテンツを取得し、次にアドレスをインクリメントします。
(*p)++ : コンテンツ+1


ポインタ変数が1回にインクリメントする量は、それが指すデータの種類に依存します。int 型を指している場合は、アドレスが int サイズのスペースずつ増加し、int 型の配列を指している場合は、その int 型配列のサイズが一度に増加します。

int arr[10];

配列名 arr は配列の先頭要素のアドレスを示す,arr, &arr は同じアドレスを出力する,&arr は配列の先頭のアドレスを示す

arr+1:アドレスが4バイト単位で増加、&arr+1:アドレスが4*10バイト単位で増加します。


配列の引数を関数に渡すと、配列はポインタになり、長さのプロパティが失われます。


文字列のコピー、コピー、比較関数を使用する場合は、長さの引数を持つ関数を使用し、メモリのオーバーフローを回避してください。


int a=10;

intはintサイズのメモリを要求していることを意味し、aはその固定メモリの名前を意味する。aはアドレスでも内容でもなく、単なる名前である。


グローバル変数はマルチスレッドに不利なので使用しないでください。要求されたスタックスペースは0に初期化し、使用するバッファはアドレスのコピーエラー(memset)を避けるために0に設定する必要があります。


定数やスタティック変数はグローバル領域にあり、ライフサイクルはプログラムのライフサイクルになります。2つのサブファンクションの2つのポインタがそれぞれ同じ定数を指している場合、どちらのポインタも同じグローバルエリア内の同じスタティックエリアを指していることになります。ヌルポインタやワイルドポインタのメモリコピーを作成することは違法です。


親関数の変数を子関数で変更することは、前のレベルの変数、第1レベルのポインタを使った普通の変数、第2レベルのポインタを使った第1レベルのポインタを使用して修正されます。普通の変数を開くのか、ポインタ変数を開くのか、ヒープ空間を開くのか、初期化するのは良い習慣です。


//strlen finds the length of the string excluding '\0'
char str1[] = "abcd"; //sizeof:5;strlen:4
char str2[] = {
'a', 'b', 'c', 'd', '\0'};//sizeof:5;strlen:4
char str3[128] = { 'x', 'y', 'z' };//default fill 0;sizeof:128;strlen:3
char *str4 = "abcd"; //sizeof:5;strlen:4; this statement means that the pointer points to a string constant
 --


配列名とポインタの違い
配列名は自己インクリメントでデータにアクセスできないが、ポインタは自己インクリメントでデータにアクセスできる。配列名+変位はポインタの自己インクリメントより効率的にデータにアクセスできる。OSは配列名に基づいて配列空間を解放する必要があるので、配列名は自己インクリメントできない。複数のポインタはヒープ空間の同じ部分を指し、ユーザー自身により解放される。


ポインタを渡す関数は、元のポインタの指す先を変えないように、受け取ったポインタが指すメモリを指すために一時変数を使わなければなりません。
ポインタが境界外に出ないように、入力されたポインタがヌルであるかどうかを判断すること。
この関数は、できる限りエクスポートを少なくして、読みやすいプログラムにしています。


0==NULL=='\0'; NULLポインタのメモリ操作はしない、ポインタを圏外に出さない、ポインタのNULLと'㊙0'の両極端を常にチェックする。


printfはプログラムによってブロックされる可能性があり fprintf(stderr,"",) は通常ブロックされません。


コンスト 修正した通常の変数は直接書けないが、ポインタを介して間接的に書いたり、強制的に変換してから代入したりできる、マクロはデバッグに参加できない定数を定義する、関数の引数を修正する場合のconstのポイントは、コードの可読性を高める(入出力引数を示す)、though数を減らすことである。

void const_test(const int *a/* read-only input argument*/)
{
    *((int*)a) = 10; // first point const has no place in c, second point strong conversion can be arbitrary in C
}


C言語の強い変換は無敵で、constは弱い。


2ビット配列とポインタ配列の内容をソートする場合、後者の方が効率的です。2ビット配列のソートは配列の中身を入れ替える必要がありますが、ポインタ配列はポインタを変更するだけなので、圧倒的に効率的です。


2次元配列はこの方法でしかソートできません:配列ポインタを使用してピックアップし、ここで6は、pが一度に6*1バイトオフセットされることを意味します。

int inverse(char (*p)[6],int n)
//int inverse(char p[][6],int n)
{
    int i=0,j=0;
    char buf[6]={
0};
    for(i=0;i<n-1;i++)
    {
        for(j=i+1;j<n;j++)
        {
            if(strcmp(p[i],p[j])<0)
            {
        // can only swap contents, not directly for 1-dimensional arrays ->p[1]=p[2] etc
                strcpy(buf,p[i]);
                strcpy(p[i],p[j]);
                strcpy(p[j],buf);
            }
        }
    }
}
Pass in an array of pointers: use a two-dimensional pointer to pick up
char *arr[5]={
"12","34","56","78","90"};
int inverse1(char* p[],int n)// default offset four units of address at a time
//int inverse1(char** p,int n)//This way is equivalent to the above, because the size of each element of the pointer array is four bytes, and each offset of a two-dimensional pointer is also four bytes.
{
    int i=0,j=0;
    char *buf=NULL;
    for(i=0;i<n-1;i++)
    {
        for(j=i+1;j<n;j++)
        {
            if(strcmp(p[i],p[j])<0)
            {
        // Since each array element is a pointer, it can be sorted either by swapping the contents or by changing the pointers' pointers.
                buf=p[i];
                p[i]=p[j];
                p[j]=buf;
            }
        }
    }
}

2次元配列を関数に渡した場合、2次元配列の要素、すなわち1次元配列のポインティングを変更することはできない。
error: array type を持つ式への代入|。


配列はデータ型なので、別名をつけることができます。

typedef int (ARRAY_INT_10) [10]; //an alias for arrays like int[10] ARRAY_INT_10
ARRAY_INT_10 b_array; //int b_array[10]; 
ARRAY_INT_10 * p = &b_array;//define an array pointer, i.e. a pointer to an array.
//equivalent to int (*a)[10] = &b_array;//array pointer
int *p[10];// array of pointers, each element is a pointer
typedef int(*ARRAY_CHAR_4_POINTER)[4]; //Define an alias for the array pointer type
ARRAY_CHAR_4_POINTER array_pointer = &array;//int array[4]={0};
int(*array_p)[4] = NULL; //define an array pointer directly, array_p is a first-class pointer class


int arr[3][4];

struct teacher {
    int id; 
    struct teacher t1; //loop definition The compiler ultimately does not determine how many bytes the structteacher takes up
};


配列 配列全体のアドレスを表します。 &array[0]==array==(array+0) は最初の要素のアドレスを表し、これは正確に等しいですが、異なる意味を持ち、型ではありません。


struct teacher {
    int id;
    struct teacher *tp;
};
struct teacher t2;
t2.id = 10;
struct teacher t1;
t1.id = 20;
t2.tp = &t1; // t2->tp = &t1

arr==&arr[0]です。 1次元配列arrを要素とする2次元配列の最初の要素へのポインタを示す。
*(arr+i)は2次元配列のi番目の要素の先頭アドレスで、1次元配列arr[i]の先頭アドレスを表わします。
( (arr+i)+j) は,1次元配列 arr[i][j] のj番目の要素の内容を表します.

縮退した、修正された入力パラメータを持つ問題。
int **p->int ***p
int arr[3]->int *arr[]==int *arr + int n //ポインタ配列の要素サイズは4バイト、2Dポインタの1シフトは4バイト、配列ポインタの1シフトも4バイトです。
int arr[3][4]->int arr[][4]==int (*arr)[4] + int n


C言語でのアライメント幅のデフォルトは8バイトです。#pragma pack(8) // 1, 2, 4, 8, 16 を使用。
構造体メンバのメモリ割り当てパターンは以下の通りです。
各メンバーについて、構造体の先頭アドレスから順に、条件を満たす先頭アドレスxを求めます。 x % N (メンバのバイト数) = 0 であり、構造体全体の長さが min{メンバの中で最大の値,システムセット:例:#pragma pack(8)}. の最小の整数の倍数。 min 十分でない場合はヌルバイトを指定する。
構造体のメンバが配列の場合、配列全体をメンバとするのではなく、配列の各要素をメンバとして割り当て、その他の割り当てルールは変わりません。
ビットセグメントがある場合の整列ルールは、ある型の記憶空間に連続して格納できる、同じ型で互いに隣接するビットセグメントのメンバは、その型のメンバ変数として扱い、その型の長さが足りなくなると、その型の長さの別の記憶空間を作り(前の未使用ビットを先に使う)、同じ型で互いに隣接しないビットセグメントのメンタは、その型の別のメンバとして扱われる、となっている。他のメンバの割り当て規則は同じで、アライメント規則も以前と同じです。


ビットシフト演算:符号なし左右シフトは0に相補、符号付き左シフトは0に相補、右ハイシフトは符号ビットに相補。符号付き数値は符号ビットを変更せずに左右にシフトします。


バイナリーファイルとテキストファイルの違い。
int型の数値をテキストとして格納する場合は、各配列や記号のASCIIを格納し、バイナリとして格納する場合は、その数値に対応するバイナリコードを格納します。また、Windowsプラットフォームとlinuxなどの他のプラットフォームでは、テキストファイルの' \n'の格納や読み込みが異なるので注意が必要です。 windows: store '\n'->'\n'; read '\rn'->'\n '. linux: store '\n'->'\n'; read '\n'->'\n'. .


文書操作モード。
r" 開く、読み取り専用、ファイルはすでに存在する必要があります。
ファイルが存在しない場合は作成し、ファイルが既に存在する場合は0バイトに切り詰めます。その後、書き換え、つまり元のファイルの内容をファイルポインタの先頭で置き換えます。
"a"は、ファイルの末尾にデータを追加するか、ファイルが存在しない場合に作成することしかできません。

注)''のようなデータは b'はバイナリテキストに対する操作であることを示し、' +例えば、'rb+' は、バイナリファイルが存在し、読み取れる場合に、バイナリファイルへの書き込みの属性を示す。


fgetc, fputc fgetcはエラーまたはファイルの終端に遭遇した場合、EOFを返します。
fputs, fgets 1行ずつ読み書きする。つまり、末尾は'˶'ᵕᴗᵕ'、1行保存するときは'˶'を削除し、1行読み込むときは'˶'を追加します。
fputsは書き込みに成功した場合は負でない数値を、失敗した場合はEOFを返します。fgetsは書き込みに成功した場合は文字へのポインタを、失敗した場合やファイルの末尾に達した場合はnullを返します。
fgets は '\n' で行を区別して読み込むことができます。
fputs は '\n' で改行し '\n' を書き出すことができます。
fwrite, fread バイナリファイルの読み出し、書き込みに使用します。


構造体の自己包含はエラーになります。

void funcA(int a, int b) //void ()(int, int)
{
    printf("funcA... \n");
    printf("a = %d, b = %d\n", a, b);
}
// Define a pointer to void ()(int, int)
void(*fp)(int, int) = funcA; //fp --> funcA
(*fp)(a, b);//equivalent to fp(a, b);//calls the function pointed to by the function pointer
void funcD(int c, void(*fp)(int, int)) ;
//fp = funcA // let a function pointer fp point to the entry address of the funcA function execution
{
    int a = 100;

    printf("funcD ... \n");
    printf("c = %d\n", c);

    // in funcD kind of call another function , let's say the function passed in, is funcD function a callback.
    fp(a, c); //indirectly calls funcA
    //funcA(a, c);

}
//FP is a kind of pointer, return value is void, 2 parameters are int, int function type pointer respectively
typedef void(*FP)(int, int);
void business(int a, int b, FP fp)
{
    //fixed business1
    printf("a = %d\n", a);
    //fixed operation 2
    printf("b = %d\n", b);
    //fixed operation 3 
    //....

    //subservice (can be configured according to the user's different implementations)
    fp(a, b);
}


静的リンクテーブルの定義。

#define STR "hello, "\
    "world\n"


注:関数に渡された任意のポインタについては、我々は彼らがヌルであるかどうかをしたい操作のポインタオブジェクト。
注:新しいノードを作成するとき、ノードの次のポインタをnullに設定したいのです。
1.ヘッドレスチェーンテーブルの初期化と破壊は、プライマリポインタの変数を変更したいので、セカンダリポインタを渡す必要があります。
2.ヘッドレス連動テーブルの特性として、初期化時に連動テーブルにメモリ空間を確保しないため、ヘッドが空かどうかの判断が必要です。
//Find this last_node, last_node->next = new_node;
for (last_node = head; last_node->next ! = NULL; last_node = last_node->next);
新しいノードをチェーンの先頭または末尾に挿入する場合、入力されるチェーンポインタはセカンダリポインタである必要があります。チェーンの末尾に挿入する場合は、チェーンテーブルが空になるのを防ぎます。チェーンの先頭に挿入する場合は、チェーンテーブルのポインターを修正する必要があります。
出力関数の入力パラメータは、関数の信頼性を高めるために、最後に修正する必要があります。
⑤ シングルリンクのテーブルで、ノードを削除する場合、(p->next == del_node) という形で削除ノードを探すことになり、指定したノードの削除が容易になりました。
(6) 操作中にノードへのポインタを渡す場合、単純にポインタ変数を用いて比較する。
(7) ヒープメモリ空間を破壊するとき、ヒープへのポインタが空かどうかを判断する必要がある。


以下はすべて、ヘッダ付きリンクテーブルとヘッダなしリンクテーブルの違いです。
ヘッダー付きのリンクテーブルの場合、ヘッダーポインターを変更する必要がないため、 挿入・削除の際に第一レベルポインターを渡すだけでよい。
(iii) ヘッダー付きリンクテーブルで、トラバーサルの際に最初の要素が head->next となる場合。


双方向のリンクテーブル
双方向リンク表の走査は、p!=headで停止します。
2.双方向リンクテーブルの初期化 head->next=head->pre=head


単方向リンク表の反転:3つのポインタp_pre, p, p_nextを使用する場合
p_pre=null;p=first_node;p_next=first_node->ネストです。
②p->nest=p_pre;p=p_nextです。
③when p_next==null;p->nest=p_pre;break;


コールバック関数。

#define FREE(p) \
do { \
    free(p);\
    p = NULL;\
} while(0)


#define STR(s) #s //#s is the code to program the s string
    printf("%s\n", STR(main()));
#define A(first, second) first##second //this splice is not a splice of strings, it's a splice of tokens
    A(printf, ("%s\n", "abc"););
#define showlist(...) \
    printf(#__VA_ARGS__) // __VA_ARGS__ would mean ... All arguments received
    showlist(dsadjsadsjksaldsjadlsajsa,dsadsad,dsadasd,dsadsa,dsad);



#define DEBUG(format, ...) \
    fprintf(stderr, "[DEBUG][%s:%d][%s][%s]" format, __FUNCTION__, __LINE__, __DATE__, __TIME__, ##__VA_ARGS__);//__VA_ARGS__ === a,b
#define ERROR(format, ...) \
    fprintf(stderr, "[ERROR][%s:%d][%s][%s]" format, __FUNCTION__, __LINE__, __DATE__, __TIME__, ##__VA_ARGS__);
#define LOG(format, ...) \
    fprintf(stderr, "[LOG][%s:%d][%s][%s]" format, __FUNCTION__, __LINE__, __DATE__, __TIME__, ##__VA_ARGS__);

ERROR("malloc p error\n");
DEBUG("a= %d, b = %d\n", a, b);
LOG("a = %d, b = %d\n", a, b);



行継続文字:'ⅷ'

#define STR "hello, "\
    "world\n"

プリプロセッサ機能には字句解析、構文チェックがなく、プリコンパイルのレビューなしに直接展開するという限界的な問題があります:。

#define FREE(p) \
do { \
    free(p);\
    p = NULL;\
} while(0)

マクロ関数を書くときは do{}while(0) は、マージン問題を防止し、複数のステートメントを全体にすることができます。


#define STR(s) #s //#s is the code to program the s string
    printf("%s\n", STR(main()));
#define A(first, second) first##second //this splice is not a splice of strings, it's a splice of tokens
    A(printf, ("%s\n", "abc"););
#define showlist(...) \
    printf(#__VA_ARGS__) // __VA_ARGS__ would mean ... All arguments received
    showlist(dsadjsadsjksaldsjadlsajsa,dsadsad,dsadasd,dsadsa,dsad);



デバッグ情報をマクロでラッピングする。

#define DEBUG(format, ...) \
    fprintf(stderr, "[DEBUG][%s:%d][%s][%s]" format, __FUNCTION__, __LINE__, __DATE__, __TIME__, ##__VA_ARGS__);//__VA_ARGS__ === a,b
#define ERROR(format, ...) \
    fprintf(stderr, "[ERROR][%s:%d][%s][%s]" format, __FUNCTION__, __LINE__, __DATE__, __TIME__, ##__VA_ARGS__);
#define LOG(format, ...) \
    fprintf(stderr, "[LOG][%s:%d][%s][%s]" format, __FUNCTION__, __LINE__, __DATE__, __TIME__, ##__VA_ARGS__);

ERROR("malloc p error\n");
DEBUG("a= %d, b = %d\n", a, b);
LOG("a = %d, b = %d\n", a, b);



dll: 最終的な実行コード
lib: コンパイラにDLLがどこにあるかを伝えます。

Windowsはまずlibファイルを探し、libファイルによってDLLがどこにあるかを知る。
注意:dllファイルは生成のみ可能で、dllファイル単独での実行(ファイルをコンパイルしてから実行し、dllファイルを生成する)はできません。lib、dllファイルが同時に存在する必要がありますので、VSコンパイラは、デフォルトでdllファイルを生成するだけで、各ダイナミックライブラリ関数の定義、宣言の前に__declspec(dllexport)を追加する必要があり、ファイルをコンパイルしてから、dllファイルを生成するファイルを実行して、この時点でエラーを報告します(それを無視)、しかしlibファイルを生成します。

自分が生成したサードパーティライブラリを誰かに渡すときは、dll、lib、.hの3つのファイルをインクルードします。


メモリリークをチェックする。
linux: valgrind
ウィンドウズ:memwatch

windowsのチェックでは、以下の動作が必要です。
memwatch の .h, .c ファイルをプロジェクトに正しく追加します。
プロジェクトのメイン関数から取得したファイルに、memwatch の .h ファイルを追加します。
プロジェクト名を右クリック Properties->C/C++->Preprocessor->Preprocessor Definition->Dropdown box select Edit->Add two macros (use two lines)を選択します。mw_stdio、memwatchの2つのマクロを追加します。