- PR -

C の localtime() の引数は混乱を招く。

投稿者投稿内容
Toshi
ベテラン
会議室デビュー日: 2003/09/12
投稿数: 62
投稿日時: 2004-09-10 18:58
引用:

コブラさんの書き込み (2004-09-10 14:10) より:
もう一つは
「何故 gcc で二つの異なる結果が出るのか」


auto 変数はスタック領域にとられるが、そこには関数を呼び出したときのリターンアドレスもかき込まれます。
明示的に初期化されないauto変数はその変数が割り中たられた場所にたまたま書かれた値が入ったままになります。
実態の無いポインタ型の変数に値を代入するということは、はたまたまそこに書かれた数字をアドレスとして参照する場所に値をかき込む亊になります。
たまたまのアドレスが書き込みがゆるされない領域の場合にはその時点でセグメンテーションエラーが出ます。
たまたまのアドレスがこれ以前にスタック領域内に記録されたリターンアドレスとかポインタ変数とかの場所だった場合、それらの値を参照したときにおかしなことになったりセグメンテーションエラーになります。
まだ使われていない領域立った場合は何もおきなかったように見えます。(プログラムを実行していく間に破壊される畏れは有ります)

しかし多分プログラムは問題のポインタ型変数を確保する前にスタックを決まった手順で使用されていると思いますので、初期化していないポインタ型変数は同じ値を示してしまいます。
多くの方が異なる環境やオプションで試されていますが、環境やオプションによりスタックへの変数の積まれ方が変わるので、値は変わって来ているのではないでしょうか。
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-09-10 19:21
>受け取り側でポインタを設定される可能性があるから、値設定されていないポインタを
>引数にとってもエラーにならないんですね。

なるほど。
人に言われて初めて気が付きました。

引数として渡して、中で実体のアドレスを受け取って抜ける、パターンもありか・・・

ちぃにぃさんのソースをパクってちょっと無茶な事しましたが。。

コード:
#include <stdio.h>
#include <stdlib.h>

static void print_unassigned_pointer(unsigned char *p)
{
    static unsigned char c = 'C';

    p = (unsigned char *)&c;
    printf("print_unassigned_pointer=%p (%c)\n", p, *p);
}

void print_unassigned_pointer_with_one_args(unsigned char *p)
{

    p = (unsigned char *)malloc(sizeof(unsigned char));
    *p = 'C';
    printf("print_unassigned_pointer_with_one_args=%p (%c)\n", p, *p);
}

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

    printf("main %p\n", p);
    print_unassigned_pointer(p);
    print_unassigned_pointer_with_one_args(p);
    free(p);
    print_unassigned_pointer(p);

    return EXIT_SUCCESS;
}



$ gcc -Wall -O -o nullpo nullpo.c
nullpo.c: 関数 `main' 内:
nullpo.c:22: 警告: `p' はこの関数内で初期化されずに使用される可能性があります
$ ./nullpo
main 0x42130a14
print_unassigned_pointer=0x804956c (C)
print_unassigned_pointer_with_one_args=0x80496c0 (C)
print_unassigned_pointer=0x804956c (C)

$ gcc -Wall -o nullpo nullpo.c
$ ./nullpo
main 0x40015360
print_unassigned_pointer=0x80495ac (C)
print_unassigned_pointer_with_one_args=0x8049700 (C)
print_unassigned_pointer=0x80495ac (C)
セグメンテーション違反です

$ gcc -Wall --omit-frame-pointer -o nullpo nullpo.c
$ ./nullpo
main 0x40015360
print_unassigned_pointer=0x80495ac (C)
print_unassigned_pointer_with_one_args=0x8049700 (C)
print_unassigned_pointer=0x80495ac (C)
セグメンテーション違反です
$

コンパイル・エラー無い、あってもワーニング程度で、見事に領域破壊。。。勿論、
Solaris8 ではこの時点でセグメンテーション違反無し。
RedHatで、この、main() の unsigned char *p; を NULL で初期化するだけで結果が
違ごてきます。

main() の

unsigned char *p; を unsigned char *p = NULL; に変更。

$ gcc -Wall -O -o nullpo nullpo.c
$ ./nullpo
main (nil)
print_unassigned_pointer=0x804958c (C)
print_unassigned_pointer_with_one_args=0x80496e0 (C)
print_unassigned_pointer=0x804958c (C)

$ gcc -Wall -o nullpo nullpo.c
$ ./nullpo
main (nil)
print_unassigned_pointer=0x80495ac (C)
print_unassigned_pointer_with_one_args=0x8049700 (C)
print_unassigned_pointer=0x80495ac (C)

$ gcc -Wall --omit-frame-pointer -o nullpo nullpo.c
$ ./nullpo
main (nil)
print_unassigned_pointer=0x80495cc (C)
print_unassigned_pointer_with_one_args=0x8049720 (C)
print_unassigned_pointer=0x80495cc (C)
$

エラー全く無し、実行結果異常無し。。。
NULL って言う実体が、あるのか無いのか・・・
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-09-10 19:37
>C言語は上記ですから、lint もまとめて考えては?

Linux の splint も結構イケますよね?
lint 系、今後は積極的に使うようにします。>MMX氏

>多くの方が異なる環境やオプションで試されていますが、環境やオプションによりスタック
>への変数の積まれ方が変わるので、値は変わって来ているのではないでしょうか。

変わりまくりです。。コンパイラ依存なんでしょうけど・・・もし実行環境が一つしか
無い場合、間違いに気づくまでに相当な時間を費やす場合もあるかと思います。

まぁ根気の要る作業ではあります。>Toshi氏
ちいにぃ
大ベテラン
会議室デビュー日: 2002/05/28
投稿数: 244
投稿日時: 2004-09-10 20:12
今回のは Electric Fence (mallocを置き換える) じゃ検出できませんが、
Purify plus とか Bounds Cheker (Windowsのみ?) とかだと、
どうなのでしょうかね‥‥(さほど C/C++してないので、使ったことないのです)

# と書いておくと、そのうち @IT に SRA さんや CompuWare さんの広告や
# セミナーの案内や入門記事が載るんではないかな、と期待してみたり。
ちいにぃ
大ベテラン
会議室デビュー日: 2002/05/28
投稿数: 244
投稿日時: 2004-09-10 22:10
ついで。不正なアドレスに書き込んだときの挙動については、
たとえば、こんなので確認できます。

----------------------------------- segv.c
コード:
#include <stdlib.h>
int
main(int argc, char *argv[]) {
	*((volatile char*)0x00010000) = 1;
	*((volatile char*)0x00001000) = 2;
	*((volatile char*)0x00000000) = 3;
	return EXIT_SUCCESS;
}



Cygwinのgcc で試すと、こうなりました:

$ gcc -g -Wall -o segv -O segv.c

$ gdb ./segv
GNU gdb 2003-09-20-cvs (cygwin-special)
(略)
(gdb) r
Starting program: /home/username/tmp/segv.exe

Program received signal SIGSEGV, Segmentation fault.
main (argc=1, argv=0xa052af8) at segv.c:7
7 *((volatile char*)0x00001000) = 2;
(gdb) q
The program is running. Exit anyway? (y or n) y
ぽんす
ぬし
会議室デビュー日: 2003/05/21
投稿数: 1023
投稿日時: 2004-09-11 01:02
なんだか「C入門講座」になってますが、その流れとは関係のないところで...
# にしても、初心者にいきなり volatile ですか

引用:

ナキヲさんの書き込み (2004-09-10 13:27) より:
不勉強で、C標準ライブラリの大元の仕様をみたことが無いので、
size_t,time_t等が本来どのように規定されているのか知らないのですが、

time_t→実装方法は規定しません
size_t→int等算術演算を行える型とします

とかその類のことが書いてあると勝手に想像しています。

もし
size_t→実装方法は規定しません
なんて規定になっているのだったら、
malloc(size_t)は
malloc(size_t*)
にしないといけないなと思ったまでです。


おっと、そこまで突っ込んだ話だったのですね。
誤解してました。失礼致しました。

とりあえず、K&R 第2版 には「time_t は時間を表す算術型であり」
と書かれていますね。

引用:

c標準ライブラリの規定ってどこで見られるかご存知ですか?


う〜ん...
http://lagendra.s.kanazawa-u.ac.jp/ogurisu/manuals/c/C-faq/C-faq-11.html
に書いてあるあたりでしょうか。
JIS の規格書は大学や研究機関の図書館に置いてあったりしますね。
ゆうじゅん
ぬし
会議室デビュー日: 2004/01/16
投稿数: 347
投稿日時: 2004-09-11 01:07
>受け取り側でポインタを設定される可能性があるから、値設定されていないポインタを
>引数にとってもエラーにならないんですね。

正確に言うと受け取り側でポインタは設定されません。

あと、エラーにならなくてもワーニングがでれば問題ないと思いますが
基本的に「不具合の原因になる可能性の警告」なのですから

まぁワーニングをすべて潰しても、「状況によって結果が違う」不具合を
未然に潰すことはできないですからコブラさんが危惧していることは
もっともだとは思います。
さんざんそれでひどい目にもあいましたので。
その分小手先のデバック技術はつきましたが(それがいいかは別として)

なので、コブラさんが例としてだしたソースですがfreeをコメントすると
NULLに初期化しない場合でも動くと思います。
あとデバック文は関数に入る前、関数内、関数からでた後の3セットで
いれるといいと思います。


コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-09-11 11:04
ゆうじゅんさん、確認しました。何か、年季を感じます。
未初期化のポインタ変数があっても、

$ gcc -Wall -O -o nullpo nullpo.c

このコンパイルオプションの時以外は、警告すら出ませんでした・・・

仮引数と実引数の番地が違うのは当然でしょうけど、
free(p); する前に malloc(); で確保した領域を呼び出し先で p に代入しても、
関数から復帰したらもう未初期化のポインタ変数の番地に戻ってもて、free(p)したい
領域を free できんのですな。。。
最上位のスタックフレームで未初期化のポインタ変数を free(p) しようとする (プ
未初期化のポインタ変数は、仮引数として渡す前に実体を伴う番地で初期化してやらないと、
Call by reference 使こても実引数になった時点で既に違う番地を指してるので、
関数から復帰後の番地の変更が伴わない・・・
やっぱり、こうするか、

コード:
#include <stdio.h>

#include <stdlib.h>

static void print_unassigned_pointer(unsigned char *p)
{
static unsigned char c = 'C';

p = (unsigned char *)&c;
printf("print_unassigned_pointer=%p (%c)\n", p, *p);
}

void print_unassigned_pointer_with_one_args(unsigned char *p)
{
p = (unsigned char *)malloc(sizeof(unsigned char));
*p = 'C';
printf("print_unassigned_pointer_with_one_args=%p (%c)\n", p, *p);

free(p);
}

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

printf("main %p\n", p);
print_unassigned_pointer(p);
print_unassigned_pointer_with_one_args(p);
print_unassigned_pointer(p);

return EXIT_SUCCESS;
}



こうするべきでしたか。。。

コード:
#include <stdio.h>

#include <stdlib.h>

static void print_unassigned_pointer(unsigned char *p)
{
static unsigned char c = 'C';

p = (unsigned char *)&c;
printf("print_unassigned_pointer=%p (%c)\n", p, *p);
}

void print_unassigned_pointer_with_one_args(unsigned char *p)
{
*p = 'C';
printf("print_unassigned_pointer_with_one_args=%p (%c)\n", p, *p);
}

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

printf("main %p\n", p);

p = (unsigned char *)malloc(sizeof(unsigned char));

print_unassigned_pointer(p);
print_unassigned_pointer_with_one_args(p);
printf("main %p, (%c)\n", p, *p);
print_unassigned_pointer(p);

free(p);

return EXIT_SUCCESS;
}



という事は、

>正確に言うと受け取り側でポインタは設定されません。

これは正しい訳ですな。



しかし、SRA と言えば、昔は unix 絡みのイベントには必ずブースを設けて
新製品もバンバンアピールしとりましたが、 Looking Glass は X.Desktop に
食われて、 Purify は ・・・ 大昔にセミナーご招待とかありましたが、最近は
どうなんですかね。PostgreSQL の教育で忙しいんかな。。。

[ メッセージ編集済み 編集者: コブラ 編集日時 2004-09-11 11:10 ]

スキルアップ/キャリアアップ(JOB@IT)