- PR -

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

投稿者投稿内容
UT
会議室デビュー日: 2004/01/02
投稿数: 1
投稿日時: 2004-09-09 20:05
コブラさんは元はJavaプログラマであったのではないかと想像します。
私はJavaからC/C++入って、最初はその微妙な違いに戸惑ったりしましたが…。

Cでは確保していない領域のポインタを渡すことは基本的にはないと思います。(暗黙の了解)
誰がその領域を管理(開放)するのという話になりますよね。
Javaならガーベッジコレクタがやってくれますが、Cにはこれが無いですから。

localtime関数のむしろ変なところは、こっちが引数として渡してもいない*tmを
返してくるところだと思います。CRTのソースを調べるとtime関連の関数が、
この領域を自前でシングルトン管理している模様ですが・・・。
同じようにポインタの変な運用をする関数にstrtokなどがあった気がします。

ポインタ引数の領域を確保するべきか、するべきじゃないかを判断する指針に
const型か非const型かがあるとおもいます。
const型のポインタに確保していない領域のアドレスの渡してあげても、
渡された関数はこの領域に対して基本的にはなにもできません。
このようなアドレスを渡すことに意味は無いわけです。

関数でわざわざconst指定しているあたりに、
「既に確保した領域を渡してあげるんだなぁ・・・」
と使用者は察してあげる必要があります。

参照うんぬんとコブラさんは申しておりました。
Javaと違ってC++にはNULLの参照はありません。
よってC++の参照渡しの場合、NULLポインタが渡せず
インスタンス/領域があることをコンパイル時に強制されます。
これは参照の1つの効用だと思います。
コブラさんが望んでおられたエラーチェック機構となるわけです。

熟練してくるとconstと参照の効用がより実感できるようになると思います。

がんばってください。
ほむら
ぬし
会議室デビュー日: 2003/02/28
投稿数: 583
お住まい・勤務地: 東京都
投稿日時: 2004-09-09 22:08
ども、ほむらです。
僕もC言語から始めた口なので特に違和感ないのですが。。。
もしかして、*& と混同していたりしませんか?
C言語でも使えましたよね?たしか。。。char * &maptextみたいな形。
-----------------
コブラ氏へ
引用:

しかし、皆さん関数の引数に '*' 付き変数があったとして、果たしてそれが
ある型のポインタ変数を指すものなのか、それともある型の実体の領域のアドレスを指す
のか瞬時に見分けられますか?


引数で求めているのはアドレスなのですから
'ある型のポインタ変数を指すものなのか'という見極めは必要ないと思います。

でもって、引数として渡す時点で内容を書き換えても反映はしませんので
実体はあるべき物ですよね?(実体のない物は戻り値または*&になるはず)

僕がポインタを説明するときによく使いますけど。
ポインタ変数なんて所詮はホテルのルームキーでしかないのですから。。。

後もう一個
>time_t なんかを typedef せんでも unsigned long の値渡しでエエっ!!
ただの想像ですけど、構造体などのunsigned longでは無くなった場合の保険じゃないですか?
まぁ、64bit化してしまえばこのままでも問題ないでしょうけど

#localtime()で不思議なのはむしろなぜアドレスでなければいけないのかですね。
#実体で良ければ localtime( time(NULL) )とかもできるのに
ぽんす
ぬし
会議室デビュー日: 2003/05/21
投稿数: 1023
投稿日時: 2004-09-10 01:25
引用:

ほむらさんの書き込み (2004-09-09 22:08) より:
> 後もう一個
> >time_t なんかを typedef せんでも unsigned long の値渡しでエエっ!!
> ただの想像ですけど、構造体などのunsigned longでは無くなった場合の保険じゃないですか?
> まぁ、64bit化してしまえばこのままでも問題ないでしょうけど


末尾に _t が付いてるのはたいていシステムデータ型で、具体的に
どういう型とするかはOSの実装にゆだねられています。
# OS の実装の詳細をプログラムから隠蔽するためにそうなってます

引用:

#localtime()で不思議なのはむしろなぜアドレスでなければいけないのかですね。
#実体で良ければ localtime( time(NULL) )とかもできるのに


Ancient Unix でカレンダー時刻を格納していたのが int time[2]
であったことの名残ではないかと思います。

[ メッセージ編集済み 編集者: ぽんす 編集日時 2004-09-10 01:42 ]
Toshi
ベテラン
会議室デビュー日: 2003/09/12
投稿数: 62
投稿日時: 2004-09-10 10:00
わたしはCでプログラミングをして頻繁におかしな結果やセグメンテーション違反を出しています。

引用:

せめて、宣言したきりで実体のアドレス代入が無いポインタを不用意に指定した場合コン
パイルエラーでも出してくれりゃエエんですがね。。。なまじ動いてるフリをするから
始末が悪い。



gccは無口ですね

コンパイル時に
-Wuninitialized -Werror -O
を付けては
ナキヲ
常連さん
会議室デビュー日: 2003/08/22
投稿数: 32
お住まい・勤務地: 京都・自宅から勤務地まで自転車で40分
投稿日時: 2004-09-10 10:06
移植性を考えて(実装に依存しない)time_tなどの
型があるのはいいんですが、

void *malloc(size_t size);

size_tはなんで値渡し!?なんだという
疑問がふと。
size_tとtime_tは規定が違うのでしょうか。

time_t time(time_t *t);
はtime_tの値を返すのだから、
localtimeも引数として値渡しでもいいんじゃないかと思います。

一貫性が無いから混乱を招くのでは。。。
歴史的事情もあるでしょうから、ある程度は仕方ないですが。
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-09-10 11:37
UT氏の

>localtime関数のむしろ変なところは、こっちが引数として渡してもいない*tmを
>返してくるところだと思います。CRTのソースを調べるとtime関連の関数が、
>この領域を自前でシングルトン管理している模様ですが・・・。
>同じようにポインタの変な運用をする関数にstrtokなどがあった気がします。

これは、全くその通りで、ANSIに問いたい。小一時間問い詰めたい。

>インタ引数の領域を確保するべきか、するべきじゃないかを判断する指針に
>const型か非const型かがあるとおもいます。
>const型のポインタに確保していない領域のアドレスの渡してあげても、
>渡された関数はこの領域に対して基本的にはなにもできません。
>このようなアドレスを渡すことに意味は無いわけです。

const 付きか否かで判断する・・・この切り口は今まで知らなかった。
今度から、ちょっと const をキーワードにして判断するようにしてみますわ。

後あの、、私の「参照渡し」は関数の Call by value と Call by reference のうちの
"Call by reference" を、そのまんま捻り無く教科書通りの「参照渡し」と書いただけで、
ちょっと C++ 系の 「参照」とはまた違うんでしょこれ?

まぁ、C++ の参照なんか使こた事無いんで違いもわかりませんが (プ
現場でも、ちょっと人によってこれ意味変わってきそうで怖い。

同じ体系の言語の機能を実装する手段として、類似名は余り使わん方がエエと思うんですがね。
C++ で使う 「継承」「多重継承」「インターフェース」も、これはこれで抽象的過ぎ
るとも思いますが。具体的な機能が掴めん・・・ともかく、「参照渡し」と「参照」は
ハッキリ言うて紛らわしい。

後、

>Javaと違ってC++にはNULLの参照はありません。
>よってC++の参照渡しの場合、NULLポインタが渡せず
>インスタンス/領域があることをコンパイル時に強制されます。
>これは参照の1つの効用だと思います。

これも重要で、活用したいですが、
これはその、、gettimeofday(&tv, NULL); の第二引数のような形は採らない、という
事なのでしょうか?



ほむら氏、久しぶり。

>引数で求めているのはアドレスなのですから

・引数には、必ず実体を伴うアドレスを、
・関数の戻り値がポインタ型なら「ただの」ポインタ変数で受けてやる。

こういう事ですな。。。

で、

>>time_t なんかを typedef せんでも unsigned long の値渡しでエエっ!!

この部分は、localtime() の引数は Call by value でエエっ!!
という事を言うてます。

これはもう、C と C++ は同時にやらん方がエエかもわからんね・・・単純名詞の意味の
受け取り方で混乱を招く (プ


Gio氏

>やはり lint でチェックしておくとか、もしくはすぐ後に代入されることが確定
>していない変数は初期化しておくとか、そういう習慣がないとトラブルに遭遇し
>やすいですね。

いや、私の場合・・・もう見ておられるかも知れませんが、 gettimeofday(); の第一引数
で値が設定される事は確実でした。そして動作も「一見」マトモやった。にも関わらず、
結果的にはバグがあった。これが私にとって大問題な訳です。

>ポインタに限らず、例えば int a; とだけ書いた場合でも a の値は実行するたびに
>変わる危険性があります。

例えば、本来 u_long a; に何らかの初期値を入れて func(&a); とするべき箇所があったと。
そこに、間違えて u_long *a; と宣言してしもた。当然、 func(a); となった。。。

func() のプロトタイプは u_long *func(u_long *);

これ、、いやこの例は悪いな。もっと具体例を。

コード:
unsigned long func(unsigned long *);
struct S *func2(struct S *);

typedef struct S {
    unsigned long sa;
    unsigned long sb;
} s;

int main(void)
{
    s aa, *bb, *rr;
    unsigned long a, *b, ret;

    aa.sa = 100L;
    aa.sb = 200L;

    rr = func2(&aa);
    ret= func(&aa.sa);
    ret= func(&aa.sb);

    bb->sa = 100L;
    bb->sb = 200L;

    rr = func2(bb);
    ret= func((unsigned long *)&bb->sa);
    ret= func((unsigned long *)&bb->sb);

    a = 100L;
    *b= 200L;
    ret = 0L;

    ret = func((unsigned long *)&a);
    printf("%u\n", ret);
    ret = func((unsigned long *)b);
    printf("%u\n", ret);
    printf("%u\n", ret);

    return(0);
}

unsigned long func(unsigned long *r)
{
    printf("%u\n", *r);
    return(*r);
}

struct S *func2(struct S *s)
{
    printf("%u\n", s->sa);
    printf("%u\n", s->sb);

    return(s);
}




私が言いたいのは、上の、

ret= func((unsigned long *)&bb->sa);
ret= func((unsigned long *)&bb->sb);

この部分でね。
なんで、Solaris8 では 無問題でバグが潜在化して、なぜ RedHat やとちゃんと
セグメンテーション・フォルト出して顕在化してくれるんか、という事でね。
セグメンテーション・フォルトは出してくれんとマズい訳で。
問題を勝手に妄想で履き違えとる人が過去二人ぐらいおられましたが、私にとって問題は逆に
これでなぜ Solaris8 はセグメンテーション・フォルト出さんのか。これ不確定要素
になるでしょ?

もっと判り易い短い例を。

コード:
#include <time.h>
#include <sys/time.h>

main()
{
    struct timeval ts;
    struct tm *tm;

    gettimeofday(&ts, NULL);
    tm = localtime((const time_t *)&(ts.tv_sec));

    tm->tm_year += 1900;

    printf("%d\n", tm->tm_year);
}



これは、Solaris8 でも RedHat でも問題無いソース。
で、なんで

コード:
#include <time.h>
#include <sys/time.h>

main()
{
    struct timeval *ts;
    struct tm *tm;

    gettimeofday(ts, NULL);
    tm = localtime((const time_t *)&ts->tv_sec);

    tm->tm_year += 1900;

    printf("%d\n", tm->tm_year);
}



こっちやとコンパイルエラーも出腐らんのに、 RedHat 側の gcc 「だけ」で
セグメンテーション・フォルトが出るのか・・・

これ、できれば誰にでも解るぐらいに説明、、して欲しいです。

っちゅぅか、なんで lint は余り活用されてこんかったのでしょうか・・・
使われてないのは俺の周りだけか? (笑)
splint なんか使った事すらない (プ

因みに、自分で何でもできない、 unsigned/signed も無い、 オーバーライドあり過ぎ
てどのスーパークラス使たらエエか判らず、メソッド使うのにどのスーパークラス
import, implement, extends するか対応が解らん Java 嫌い (笑)

すぐ関数作れる C, C++ の方がいいです。
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-09-10 12:04
危惧する部分は、実体が無いポインター変数だけ宣言して、それを引数に使うと。
その場合、先ほど止めた例で、、

>例えば、本来 u_long a; に何らかの初期値を入れて func(&a); とするべき箇所が
>あったと。そこに、間違えて u_long *a; と宣言してしもた。当然、 func(a);
>となった。。。 func() のプロトタイプは u_long *func(u_long *);

この時、実体の無いポインター変数であっても、u_long が 32bit として、
ポインターを確保するのに必要な領域も 32bit とすると、多分動いてしまう。
これをチェックする方法が欲しい。と思っていたら、

Toshi氏:

> -Wuninitialized -Werror

やってみました。

>cc1: 警告はエラーとして取り扱われます
>関数 `main' 内:
>6: 警告: `ts' はこの関数内で初期化されずに使用される可能性があります


>cc1: 警告はエラーとして取り扱われます
>関数 `main' 内:
>11: 警告: `bb' はこの関数内で初期化されずに使用される可能性があります
>12: 警告: `b' はこの関数内で初期化されずに使用される可能性があります

これ、イケますな!
っちゅぅか、変数 `b' は初期化されても「されてない」扱い (プ
厳しいぐらいが丁度エエかも・・・

しかし、Solaris8 の gcc3.2で, 実行できてまう・・・
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-09-10 12:08
>time_t time(time_t *t);
>はtime_tの値を返すのだから、
>localtimeも引数として値渡しでもいいんじゃないかと思います。

全く、ごもっともな意見です。
私もそう思います。

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