Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

C言語 コマンドライン引数にfor文を使って配列を入れたい

解決したいこと

コマンドライン引数に配列を入れたい。

C言語の練習でswap関数を作り、配列をソートする関数を作った。配列をmain関数に最初入れていたが、より応用性を求めるためにコマンドライン引数を用いたいと考えた。どうしてsegmentaion faultが起きるのか、このプログラムで何が起きているのかと具体的な解決策を教えていただきたい。

発生しているエラー

ファイル名がexample.cなのでcc example.c -o exampleとコンパイルする。するとエラーは出てこないが、
./exampleと実行するとsegmentation faultが起きる。

発生している問題・エラー

コンパイルはできるが、実行するとsegmentation faultが起きる。

segmentation fault

該当するソースコード

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

int main(int argc, char* argv[]){
    int i;
    int x[argc-1];
    for(i=0;i<=argc-1;i++){
        x[i] = atoi(argv[i+1]);
    }
    for(i=0;i<=argc-1;i++){
        printf("%d ",x[i]);
    }
    retun 0;
}

自分で試したこと

C言語の練習でswap関数を作り、配列をソートする関数を作った。配列をmain関数に最初入れていたが、より応用性を求めるためにコマンドライン引数を用いたいと考えた。どうしてsegmentaion faultが起きるのか、このプログラムで何が起きているのかと具体的な解決策を教えていただきたい。

0 likes

3Answer

配列xargc-1個の要素を確保しているので、有効な範囲はx[0]からx[argc-2]までです。
0からargc-2まででargc-1個になることをよく理解してください。

forループではx[i]にアクセスしていますね。
そしてループ変数iは0からargc-1までとなっています。
argc-2までは大丈夫ですが、最後のargc-1で有効範囲を超えてしまい、segmentation faultとなるわけです。

起こっているのは、そういうことです。

なお、argv[0]には通常プログラム名が入っており、コマンドライン引数はargv[1]からargv[argc-1]までのargc-1個です。

1Like

落ちる理由

これらの調査には、gdbというデバッガーを使うのが良いです。

このあたりはgoogleで検索いただければと思います。

引数なしの場合(./example)

(gdb) p argc
$1 = 1
(gdb) p argv[0]
$2 = 0x7fffffffe3fe "/home/kmtr/work/study1229/example"
(gdb) p argv[1]
$3 = 0x0

引数ありの場合(./example 1 2 3 )

(gdb) p argc
$3 = 4
(gdb) p argv[0]
$4 = 0x7fffffffe3f8 "/home/kmtr/work/study1229/example"
(gdb) p argv[1]
$5 = 0x7fffffffe41a "1"
(gdb) p argv[2]
$6 = 0x7fffffffe41c "2"
(gdb) p argv[3]
$7 = 0x7fffffffe41e "3"
(gdb) p argv[4]
$8 = 0x0

どこで問題が起きるのか

問題が起きる原因は、ここの配列指定にあります。

    for(i=0;i<=argc-1;i++){
        x[i] = atoi(argv[i+1]);
    }

./example 1 2 3 で実行した場合、当該ループはargc=4なので、(i<=argc-1)の条件を満たす間、i=0, 1, 2, 3(argc-1)で繰り返されます。

    for(i=0;i<=argc-1;i++){

ループ内のアクセスは、argv[i+1] なので、argv[1], argv[2], argv[3], argv[4]にアクセスがあります。

        x[i] = atoi(argv[i+1]);

よって、atoi(argv[4]) を実行するとatoi(NULL)となり、不正メモリアクセスとなります。

乱暴な解決策

もっといい解決策を誰か出してくれるかもしれませんが。

argv[0]はどうせプログラム名が入っているので無視する、という手もあります。

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

int main( int argc, char* argv[] ) {
    int i;
    int x[argc-1];

    if ( argc == 1 ) {
        return 0; // no argument.
    }

    /* skip first element because it is program name */
    argv++;
    argc--;

    for ( i = 0; i < argc; i++) {
        x[i] = atoi(argv[i]);
    }

    for ( i = 0; i < argc; i++) {
        printf("%d ",x[i]);
    }
    return 0;
}

1Like

./exampleと実行するとsegmentation faultが起きる。

学習目的でプログラミングをするのであればデバッガを使って1行づつトレースするのが意味を理解しやすいと思います。

(gdb) break main
Breakpoint 1 at 0x80488af: file a.c, line 5.
(gdb) run
Starting program: /tmp/a.out

Breakpoint 1, main (argc=1, argv=0xbfbfe674) at a.c:5
5       int main(int argc, char* argv[]){
(gdb) step
7           int x[argc-1];
(gdb) step
8           for(i=0;i<=argc-1;i++){
(gdb) step
9               x[i] = atoi(argv[i+1]);
(gdb) print argv[i+1]
$1 = 0x0
(gdb) step

Program received signal SIGSEGV, Segmentation fault.
0xbba351a1 in ?? () from /usr/lib/libc.so.12

これは1行づつ実行したものだが、8行目のループ条件が想定通りのものか確認していただきたい。引数を渡さず実行した場合、このループに入らない(=順序を入れ替えるものはない)はずだ。

しかし、引数を与えない場合 argc には、1が入っている。
従って、

for(i=0;i<=argc-1;i++)
↓   ↓   ↓  
for(i=0;i<=1-1;i++)

初回のループは
0 <= 0
で判定されるためにループの中に入ってしまう。
引数も無いのにこの処理に入ってしまうとこの時の argv[1] は変数値を確認したように文字列の先頭をさすものではなく、NULLへのポインタとなっている。atoi()が想定しているものではなく、文字列として処理できないために segmentation faultが発生する。

解決策としては2か所ある for()の条件を<= -> <に変えるのが良いだろう。

という感じでしょうか。

0Like

Your answer might help someone💌