C言語の主な機能の詳細な分析!

メインの戻り値

main関数の戻り値は、プログラムの終了ステータスを説明するために使用されます。0を返すと、プログラムは正常に終了します。返される他の番号の意味は、システムによって決定されます。通常、ゼロ以外のリターンは、プログラムが異常終了したことを意味します。

void main()

一部の本はvoidmain()を使用していますが、これは間違っています。ボイドmain()はC / C ++で定義されたことはありません。

C ++の父であるBjarneStroustrupは、彼のホームページのFAQで、「定義void main(){/ *…* /}はC ++ではなく、C ++でもなかったし、Cでもなかった」と明確に述べています。 CおよびC ++では、パラメーターを受け取らず、情報を返さない関数のプロトタイプは「void foo(void);」であるためです。

おそらくこれが原因で、プログラムの戻り値が必要ない場合、main関数をvoid main(void)として定義できると誤解している人がたくさんいます。しかし、これは間違っています!main関数の戻り値は、CおよびC ++標準で指定されているように、int型として定義する必要があります。

一部のコンパイラではvoidmain()をコンパイルできますが、void mainが標準で定義されたことがないため、すべてのコンパイラがvoid main()をサポートしているわけではありません。

g ++ 3.2では、main関数の戻り値がint型でない場合、コンパイルはまったく渡されません。そしてgcc3.2は警告を発します。したがって、プログラムの移植性を高めるには、int main()を使用する必要があります。テストは次のとおりです。

#include <stdio.h>

void main()
{
    printf("Hello world\n");
    return;
}

操作結果:g ++ test.c

メイン()

main関数の戻り値のタイプは1つしかないので、省略できますか?規制:戻り値が明確にマークされていない場合、デフォルトの戻り値はintです。これは、main()がvoid main()ではなく、int main()と同等であることを意味します。

C99では、標準ではコンパイラーが少なくともmain()の使用について警告を出すことを要求していますが、C89ではこの種の書き込みが許可されています。ただし、プログラムの標準化と読みやすさのために、戻り値のタイプを明確に指摘する必要があります。テストコード:

#include <stdio.h>

main()
{
    printf("Hello world\n");
    return 0;
}

動作結果:

CおよびC ++標準

C99標準では、次の2つの定義のみが正しいです。

int main( void ) 
int main( int argc, char *argv[] ) 

コマンドラインからパラメータを取得する必要がない場合は、int main(void);を使用します。それ以外の場合は、int main(int argc、char * argv [])を使用します。もちろん、パラメータを渡す方法は他にもあります。これについては、次のセクションで個別に説明します。

メイン関数の戻り値タイプはintである必要があります。これにより、戻り値をプログラムの呼び出し元(オペレーティングシステムなど)に渡すことができます。これは、関数の実行結果を決定するためのexit(0)と同等です。

C ++ 89では、次の2つの主要な関数が定義されています。

int main( ) 
int main( int argc, char *argv[] ) 

int main()は、C99のint main(void)と同等です。intmain(int argc、char * argv [])の使用法も、C99で定義されているものと同じです。同様に、main関数の戻り値タイプもintである必要があります。

リターンステートメント

main関数の最後にreturnステートメントが記述されていない場合、C99とC ++ 89はどちらも、コンパイラが生成されたオブジェクトファイルにreturn 0を自動的に追加することを規定しており、プログラムが正常に終了することを示します。

ただし、main関数の最後にreturnステートメントを追加することをお勧めします。これは必須ではありませんが、良い習慣です。Linuxでは、シェルコマンドecho $?を使用して関数の戻り値を表示できます。

#include <stdio.h>

int main()
{
    printf("Hello world\n");
}

動作結果:

同時に、returnの戻り値は型変換を受けることに注意してください。たとえば、return 1.2の場合、1に強制されます。つまり、真の戻り値は1です。同じことで、「a」を返します。trueの場合、return値は97ですが、「abc」を返すと、暗黙的な型変換を実行できないため、警告が報告されます。

主な関数の戻り値の意味をテストします

前述のように、main関数が0を返す場合は、プログラムが正常に終了することを意味します。通常、ゼロ以外のリターンは、プログラムが異常終了したことを意味します。この記事の最後で、それをテストします:test.c:

#include <stdio.h>

int main()
{
    printf("c 语言\n");
    return 11.1; 
}

ターミナルで以下を実行します。

➜  testSigpipe git:(master) ✗ vim test.c
➜  testSigpipe git:(master) ✗ gcc test.c
➜  testSigpipe git:(master) ✗ ./a.out && echo "hello world"  #&&与运算,前面为真,才会执行后边的
c 语言

main関数の戻り値が11であるため、オペレーティングシステムはmain関数が失敗したと見なしていることがわかります。

➜  testSigpipe git:(master) ✗ ./a.out 
➜  testSigpipe git:(master) ✗ echo $?
11

main関数の戻り値を0にする必要がある場合:

➜  testSigpipe git:(master) ✗ vim test.c
➜  testSigpipe git:(master) ✗ gcc test.c 
➜  testSigpipe git:(master) ✗ ./a.out && echo "hello world" #hello
c 语言
hello world

予想どおり、メイン関数は0を返します。これは、関数が正常に終了して実行が成功したことを意味します。ゼロ以外を返した場合は、関数が異常で実行が失敗したことを意味します。

パラメータを渡す主な関数

最初に注意すべきことは、main関数をパラメーターに渡すことができないと考える人もいるかもしれませんが、実際にはこれは間違っています。main関数はコマンドラインからパラメータを取得できるため、コードの再利用性が向上します。

関数プロトタイプ

メイン関数のパラメーターを渡す場合、オプションのメイン関数のプロトタイプは次のとおりです。

int main(int argc , char* argv[],char* envp[]);

パラメータの説明:

①。最初のパラメータargcは、着信パラメータの数を表します。

②。2番目のパラメータchar * argv []は文字列配列であり、文字列パラメータへのポインタ配列を格納するために使用されます。各要素はパラメータを指します。各メンバーの意味は次のとおりです。

argv [0]:プログラムのフルパス名を指します。

argv [1]:実行中のプログラムの名前の後の最初の文字列を指します。これは、実際に渡された最初のパラメーターを表します。

argv [2]:実行中のプログラムの名前の後の2番目の文字列を指します。これは、渡された2番目のパラメーターを表します。

……argv [n]:渡されたn番目のパラメーターを表す、実行中のプログラムの名前の後のn番目の文字列を指します。

規制:argv [argc]はNULLであり、これはパラメーターの終わりを意味します。

③。3番目のパラメータchar * envp []も文字列配列であり、主にユーザー環境で変数文字列を保存し、NULLで終わります。envp []の各要素には、ENVVAR = valueの形式の文字列が含まれています。ここで、ENVVARは環境変数であり、valueはそれに対応する値です。

envpが渡されると、それは単なる文字列配列であり、プログラムの動的設定によって変更されることはありません。putenv関数を使用して環境変数をリアルタイムで変更できます。また、getenvを使用して環境変数をリアルタイムで表示することもできますが、envp自体は変更されません。通常はあまり使用されません。

:メイン関数のパラメーターchar * argv []およびchar * envp []は文字列配列を表し、書き込み形式はchar * argv []以上であり、対応するargv [] []およびchar ** argvは次のようになります。 。

char * envp []

main関数の3番目のパラメーターをテストする小さなテストプログラムを作成します。

#include <stdio.h>

int main(int argc ,char* argv[] ,char* envp[])
{
    int i = 0;

    while(envp[i++])
    {
        printf("%s\n", envp[i]);
    }

    return 0;
}

実行結果:部分的なスクリーンショット

envp []によって取得される情報は、Linuxでのenvコマンドの結果と同等です。

共通バージョン

main関数のパラメーター化されたバージョンを使用する場合、最も一般的に使用されるのは次のとおりです。** int main(int argc、char * argv []); **変数名argcおよびargvは従来の名前ですが、もちろん他の名前に置き換えることができます。 。

コマンドライン実行の形式は次のとおりです。実行可能ファイル名パラメーター1パラメーター2……パラメーターnスペースを使用して、実行可能ファイル名、パラメーター、およびパラメーターを区切ります。

サンプルプログラム

#include <stdio.h>

int main(int argc, char* argv[])
{

    int i;
    printf("Total %d arguments\n",argc);

    for(i = 0; i < argc; i++)
    {
        printf("\nArgument argv[%d]  = %s \n",i, argv[i]);
    }

    return 0;
}

動作結果:

➜  cpp_workspace git:(master) ✗ vim testmain.c 
➜  cpp_workspace git:(master) ✗ gcc testmain.c 
➜  cpp_workspace git:(master) ✗ ./a.out 1 2 3    #./a.out为程序名 1为第一个参数 , 2 为第二个参数, 3 为第三个参数
Total 4 arguments
Argument argv[0]  = ./a.out 
Argument argv[1]  = 1 
Argument argv[2]  = 2 
Argument argv[3]  = 3 
Argument argv[4]  = (null)    #默认argv[argc]为null

メインの実行順序

言い換えれば、メイン関数はプログラムによって実行される最初の関数でなければならないと言う人もいるかもしれません。それで、これは本当に本当ですか?このセクションを読んだ後、別の理解があると思います。

main()がプログラムの入り口であると言われる理由

Linuxシステムでのプログラムのエントリポイントは「_start」です。この関数はlinuxシステムライブラリ(Glibc)の一部であり、プログラムとGlibcがリンクされて最終的な実行可能ファイルを形成する場合、この関数はプログラム実行初期化のエントリ関数です。 。テストプログラムを通じて説明するには:

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}

コンパイル:

gcc testmain.c -nostdlib     # -nostdlib (不链接标准库)

プログラムの実行によりエラーが発生します:/ usr / bin / ld:警告:エントリシンボル_startが見つかりません;このシンボルが見つかりません

そう:

  1. コンパイラはデフォルトで、メインではなく__startシンボルを検索します

  2. __startこの記号はプログラムの開始です

  3. mainは、標準ライブラリによって呼び出されるシンボルです。

では、この_startとmain関数の関係は何ですか?以下でさらに詳しく見ていきましょう。

_start関数の実現エントリは、ldリンカーのデフォルトのリンクスクリプトによって指定されます。もちろん、ユーザーはパラメーターを使用して設定することもできます。_startはアセンブリコードによって実装されます。これは、次の疑似コードで大まかに表されます。

void _start()
{
  %ebp = 0;
  int argc = pop from stack
  char ** argv = top of stack;
  __libc_start_main(main, argc, argv, __libc_csu_init, __linc_csu_fini,
  edx, top of stack);
}

対応するアセンブリコードは次のとおりです。

_start:
 xor ebp, ebp //清空ebp
 pop esi //保存argc,esi = argc
 mov esp, ecx //保存argv, ecx = argv

 push esp //参数7保存当前栈顶
 push edx //参数6
 push __libc_csu_fini//参数5
 push __libc_csu_init//参数4
 push ecx //参数3
 push esi //参数2
 push main//参数1
 call _libc_start_main

hlt

_startを呼び出す前に、ローダーがユーザーのパラメーターと環境変数をスタックにプッシュすることがわかります。

メイン関数が実行される前に作業する

_startの実装から、main関数を実行する前に一連の作業を行う必要があることがわかります。主なことは、システム関連のリソースを初期化することです。

Some of the stuff that has to happen before main():

set up initial stack pointer 

initialize static and global data 

zero out uninitialized data 

run global constructors

Some of this comes with the runtime library's crt0.o file or its __start() function. Some of it you need to do yourself.

Crt0 is a synonym for the C runtime library.

1.スタックポインタを設定します

2.静的静的およびグローバルグローバル変数、つまりデータセクションのコンテンツを初期化します

3.初期化されていない部分に初期値を割り当てます。数値タイプshort、int、longなどは0、boolはFALSE、ポインターはNULLなど、つまり.bssセクションの内容です。

4. C ++のグローバルコンストラクターと同様に、グローバルコンストラクターを実行します

5. main関数、argc、argvなどのパラメーターをmain関数に渡してから、実際にmain関数を実行します。

メインの前に実行されるコード

以下では、mian関数が実行される前に実行されるコードについて説明します。(1)グローバルオブジェクトのコンストラクターは、main関数の前に実行されます。

(2)一部のグローバル変数、オブジェクト、静的変数、オブジェクトのスペース割り当てと初期値割り当ては、メイン関数の実行前であり、メイン関数の実行後に、スペースの解放やリソース使用権の解放などの操作を実行する必要があります。

(3)プロセスの開始後、初期化コード(環境変数の設定など)を実行してから、mainにジャンプして実行する必要があります。グローバルオブジェクトの構築もメインの前にあります。

(4)キーワード属性を使用して、関数をメイン関数の前に実行し、データの初期化、モジュールのロード検証などを実行します。

サンプルコード

①、キーワード属性を介して

#include <stdio.h>

__attribute__((constructor)) void before_main_to_run() 
{ 
    printf("Hi~,i am called before the main function!\n");
    printf("%s\n",__FUNCTION__); 
} 

__attribute__((destructor)) void after_main_to_run() 
{ 
    printf("%s\n",__FUNCTION__); 
    printf("Hi~,i am called after the main function!\n");
} 

int main( int argc, char ** argv ) 
{ 
    printf("i am main function, and i can get my name(%s) by this way.\n",__FUNCTION__); 
    return 0; 
}

②グローバル変数の初期化

#include <iostream>

using namespace std;

inline int startup_1()
{
    cout<<"startup_1 run"<<endl;
    return 0;
}

int static no_use_variable_startup_1 = startup_1();

int main(int argc, const char * argv[]) 
{
    cout<<"this is main"<<endl;
    return 0;
}

この時点で、メイン関数が実行される前に話を終えましたが、メイン関数はプログラムが実行する最後の関数でもあると思いますか?

もちろん、結果はそうではありません。メイン関数の実行後、実行できる他の関数があります。メイン関数の実行後、エントリ関数に戻り、エントリ関数は、グローバル変数の破棄、ヒープの破棄、I / Oのシャットダウンなどのクリーンアップ作業を実行し、次に進みます。システム呼び出しはプロセスを終了します。

主な機能の後に実行される機能

1.グローバルオブジェクトのデストラクタはメイン関数の後に実行されます。2。atexitに登録されている関数もメイン関数の後に実行されます。

atexit関数

プロトタイプ:

int atexit(void (*func)(void)); 

atexit関数は、関数を「登録」して、メイン関数が正常に終了したときにこの関数が呼び出されるようにすることができます。プログラムが異常終了した場合、プログラムを介して登録された関数は呼び出されません。

コンパイラは、プログラマが少なくとも32の関数を登録できるようにする必要があります。登録が成功した場合、atexitは0を返します。それ以外の場合は、ゼロ以外の値を返し、関数の登録をキャンセルする方法はありません。

終了によって実行される標準のクリーンアップ操作の前に、登録された関数は、登録順序の逆の順序で順番に呼び出されます。呼び出された各関数はパラメーターを受け入れず、戻り値のタイプは無効です。登録された関数は、それ自体で定義されていない限り、ストレージクラスが自動またはレジスタ(たとえば、ポインタ)であるオブジェクトを参照しようとしないでください。

同じ関数を複数回登録すると、この関数が複数回呼び出されます。関数呼び出しの最後の操作は、ポッププロセスです。main()も関数です。最後に、atexit関数はスタックからポップアウトする順序で呼び出されます。したがって、関数atexitは、関数のスタックおよびポップと同じ関数であり、first-in-last-outであり、最初に登録されます。実行後。コールバッククリーンアップ関数は、atexitを介して登録できます。これらの関数に、メモリの解放、開いているファイルのクローズ、ソケット記述子のクローズ、ロックの解放などのクリーンアップ作業を追加できます。

#include<stdio.h>
#include<stdlib.h>

void fn0( void ), fn1( void ), fn2( void ), fn3( void ), fn4( void );

int main( void )

{
  //注意使用atexit注册的函数的执行顺序:先注册的后执行
    atexit( fn0 );  
    atexit( fn1 );  
    atexit( fn2 );  
    atexit( fn3 );  
    atexit( fn4 );

    printf( "This is executed first.\n" );
    printf("main will quit now!\n");

    return 0;

}

void fn0()
{
    printf( "first register ,last call\n" );
}

void fn1(
{
    printf( "next.\n" );
}

void fn2()
{
    printf( "executed " );
}

void fn3()
{
    printf( "is " );
}

void fn4()
{
    printf( "This " );
}

著者:z_ryan

オリジナル:https://blog.csdn.net/z_ryan/category_7316855.html


1. GD32 Arm MCU Internet of Things開発者オンラインコースのエキサイティングなコンテンツのプレビュー!

2.ヤン・フユコラム|追い抜くことができるコーナーを探す:偉人は真ん中にいると言った

3. RISC-Vは実際にはトレンドに反しています!

4.無視しないでください!埋め込みコードの致命的な抜け穴!

5.エクストラ!それ以来、LoRa長距離通信「常駐」MCU

6.テクノロジーは本当に中立ですか?

免責事項:この記事はオンラインで複製されており、著作権は元の作者に帰属します。著作権の問題が発生した場合は、お問い合わせください。提供された著作権証明書に基づいて著作権を確認し、作者の報酬を支払うか、コンテンツを削除します。

おすすめ

転載: blog.csdn.net/DP29syM41zyGndVF/article/details/112690324