- PR -

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

投稿者投稿内容
ほろりん
ベテラン
会議室デビュー日: 2004/11/24
投稿数: 98
お住まい・勤務地: あそこ
投稿日時: 2005-01-14 18:32
引用:

どぅさんの書き込み (2005-01-14 15:28) より:
#ややこしいですが、普通の変数のみならず、ポインタ変数も参照渡しできると思います(多分)。
#その場合、ポインタ変数自身を書き換えることができるのでしょう。。


わしは引退した身じゃて、ジャヴァとかしぃぷらぷらとかいう新型の目的指向型電脳用言語は知らんが、住所指示変数(ぽゐんたぁ)も参照渡しができないとおかしいですじゃ。
さっきも言ったんじゃが、住所指示変数は、単に記憶領域の住所が入っているだけで普通の変数と変わらないはずじゃ。何もややこしいことはないはずじゃ。
どぅ
会議室デビュー日: 2005/01/13
投稿数: 4
投稿日時: 2005-01-14 21:59
C++の参照渡しへのフォローどうもです。>みなさま

引用:

これは、もし x が 構造体ならば、構造体の先頭アドレスを渡してやる訳で、
time(); とか localtime_s(); の引数でも同じではないのですか?



構造体も普通のint型の変数も、関数への受け渡しにおいては扱いは同じです。(配列はちと違います)

すんません、time()関数についてだけ、記述します。プロトタイプはこんな感じでしょうか。。

time_t time(time_t *tptr)

です。これの仕様は、

1:引数には、ある領域のアドレスをもらう。
2:現在の時刻を表すtime_t型の値を返り値として返す。
3:引数で与えられたアドレスの指す領域に、time_t型の2と同じ値を書き込む

です。今は引数が話題になっているので、返り値のことは無視しましょう。とすると、普通、これはこのように呼び出します。

コード:
time_t timeVal;

time(&timeVal);



これで、変数timeValに、現在時刻を表すある値が書き込まれます。なぜなら、time()関数は、&timeValという記述によって、timeValの「アドレス」を教えてもらったからです。逆に言うと、time関数は、与えられたアドレスに書き込んだだけで、そこがtimeValという変数の領域だということには全然知りません。

また、こう書くこともできるでしょう。同じことです。

コード:
time_t timeVal;
time_t *p_time;

p_time = &timeVal;

time(p_time);



こうしても、変数timeValに現在時刻を表す値が書き込まれます。なぜなら、time()関数は、p_timeという記述によって、timeValのアドレスを教えてもらったからです。time関数側では、timeValという変数の存在も、p_timeというポインタ変数の存在も見えません。分かるのは「アドレス」だけです。結局これによって変更されるのは、timeValの値であって、p_timeの値は変らないという点に注意してください。

あたり前ですが、参照渡しのできないCでは、次のように書いても、timeValに値をもらうことはできません。

コード:
time_t timeVal;

time(timeVal);



timeValの値は全然変化しないでしょう。(まぁ引数の型がちがってますが^^;)

さて、ここでこのスレッドの一番最初の話にもどります。(関数は違うけど^^;)

コード:
time_t *p_time;

time(p_time);



こうしたときに、何が起こるのでしょうか?これはまずいコーディングですが、なぜまずいのでしょうか?

まず、p_timeの値は書き換わりません。値渡しなので、引数は影響を受けないのです。

time()関数側から見ると、アドレスが渡るとしか思っていません。p_timeに書かれた値が、time関数に渡されます。どんな値が渡されるのでしょうか?p_timeが格納されている領域のアドレスでしょうか?そうではないですね。p_timeが格納されている領域のアドレスは&p_timeです。ではどんな値でしょうか?p_timeにはどんな値が入っているのでしょうか?

それは、p_timeを宣言して領域確保したときに「偶然その領域に書いてあった値」ですね。たとえば、以前の名残から7と書いてあったら、p_timeには7という値が入っています。つまりp_timeは7番地を指している。7番地の領域って、どんな領域でしょうか?さぁ分かりません^^;しかし、time()関数はそんなことは知りません。7という値を受け取り、それをアドレスだと思うだけです。なので、time()関数は7番地にアクセスし、7番地にtime_t型の値を書き込みます。その際、問答無用に書き込みます。領域なんて気にしません。確保もしません。

この場合、その領域の状況しだいで、セグメンテーションフォールトが発生することがあります。書き込まれる領域のアドレスは、「p_timeの領域に偶然書かれていた値」番地です。

難しい話ではありません。適当なアドレスを関数に渡せば、関数は知らずにその適当なアドレス領域にアクセスして、もしかしたらセグメンテーションフォールトを起こすし、もしかしたらうまく動きます。

こういうのは、普通バグといいます。これを避けるためには、アドレスを渡す際には、そのアドレスの先がちゃんと確保されていることを確認すること。それから、補助的ですが有効な手段として、コンパイラの警告レベルを上げて初期化漏れをなくすこと。です。

別にポインタに限った話ではありません。宣言しただけで正しい値を代入していない変数を関数に渡す(←これは値渡しという意味ですよ)というのはバグの元です。

以上です。
MMX
ぬし
会議室デビュー日: 2001/10/26
投稿数: 861
投稿日時: 2005-01-15 09:51
おもしろい、状況を正確に理解する 手段としてマシンの 意味論で説明したほうが
言語が作り出した「変数・引数渡し・ポインタ・・・」よりいいなんて。
C 言語の概念・意味の抽象度が中途半端なのかもしれないが。
型のある世界にもかかわらず、型破壊光線がいろいろあると言うこと。

[ メッセージ編集済み 編集者: MMX 編集日時 2005-01-31 10:25 ]
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2005-01-15 11:40
この手の、顕在化しないバグについては、今のところ Warning レベルを上げるとか
Purify とかのツールを使うかの何れか、という事になるでしょうか。

そこが明らかになっただけでも私にとっては利益というべきです。
いや、皆さんありがとうございました。

後、ナキヲさんの、"C++ の「参照」は実体のある変数でないとエラー"、

>これだけではなくて、C++の"参照引数(&)"には、
>ポインタとの決定的な違いがあります。
>『引数型の"実体のある変数"しか渡せない』という制限です。

>引数がポインタ型なら、
>func(NULL);
>とか
>func(777);//適当
>なんてことが出来てしまいますが、
>参照型引数には、なんらかの実体のある変数
>を渡さないとコンパイルエラーになります。

>参照先が不正なアドレスで落ちる、ということが防げるわけです。

これは目から鱗が落ちました。
_________________
日本の中心で、オフを叫ぶ(@名古屋)。 ご意見募集中!
コブラ
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2005-01-16 21:52
まぁ、お礼という程のものでもないですが、ささやかながら
こんなモンでも・・・

http://202.226.153.50/cobra/public_html/ieri/images/episode3.mov

どうぞ。私は、20世紀フォックスに貢献します。
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2005-08-24 02:32
こっちの方が行数も少ないし単純なので、こっちにしましょう。

コード:
#include <stdlib.h>

#include <math.h>
#include <time.h>
#include <sys/time.h>

#define _ISOC99_SOURCE

#define DAYS (60 * 60 * 24)
#define NORM (DAYS * 365)
#define LEAP (DAYS * 366)
#define EPOC 1970

typedef unsigned int bool;

static const bool TRUE = 1;
static const bool FALSE= 0;

short ttl[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
short mon[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
enum date {Thu, Fri, Sat, Sun, Mon, Tue, Wed};

///////////////////////////////////////////////////////////////////
// localtime_s()
// 機能 : 年・月・日・曜日・時・分・秒・通算経過日数・夏時間を出力
//
// 入力 :
// 第一引数: *tm
// 型: tm 構造体へのポインター
// 第二引数: t
// 型: unsigned long
//
// 出力 : *tm
// 型: tm 構造体へのポインター
//
// 戻り値 : 論理型
// TRUE : 1
// FALSE: 0
//
///////////////////////////////////////////////////////////////////
bool localtime_s(struct tm *tm, time_t t)
{
enum date wday = Thu;
time_t now;
unsigned long tmp = 0L, temp=0L, leap=0L;
unsigned int y, m, month, h, mn, s, c, zg, rem, cur;
double d, days, gap;
short i=0;
char *TZ, tz[4];
char flg = '0';

/* 初期化 */
y = m = month = h = mn = s = c = zg = rem = cur = 0;
d = days = 0.0000;

/* 1970年1月1日から指定年度まで */
for(tmp = 0; tmp <= t; tmp += NORM){
if(tmp + NORM >= t){
tmp -= NORM;
break;
}

c = y + EPOC;

if(!(c % 4) && ((c % 100) || !(c % 400))){
leap++;
}

y++;
}

leap *= DAYS;
gap = (double)t - (tmp + leap);

temp = Sun;
temp += t / DAYS;
if(!(c % 4) && ((c % 100) || !(c % 400))) temp++;
temp %= 7;

if(gap > 0.000) days = gap / DAYS;
else days = 1;

for(i = 1; i <= 12; i++){
if(gap > (ttl[1] * DAYS)){
if(gap <= (ttl[i + 1] * DAYS)){
month = i + 1;
d = (double)(gap / DAYS) - ttl[i];

if(d >= 0.000 && d < 1.000) d = 1.000;
else d++;

temp++;

break;
}
}
else {
if(gap > 0.000){
d = (double)(gap / DAYS);
d++;
}
else d = 1.000;
temp++;

month = i;

break;
}
}

/* 指定時刻時・分・秒の割出し */
h = t % DAYS;
mn = h % (60 * 60);
s = mn % 60;

h /= (60 * 60);
mn /= 60;

/* タイムゾーン増減 */
TZ = (char *)getenv("TZ");
memset(tz, 0x00, sizeof(tz));
strncpy(tz, (TZ + 3), 3);

/* atoi() */
for(i=1, zg=0; i <= strlen(&tz[1]); i++){
zg *= 10;
zg += (tz[i] - 0x30);
}

if(h + zg >= 24){
if(month == 12 && d == (double)mon[month]){
c++;

month = 1;
wday = 1;
d = 1.000;
days = 1.000;
}
else {
d = d + 1.000;
// 月末調整
if(d > mon[month] + 1){
month++;
d = 1.000;
}

days++;
}

temp++;
}

/*
time(&now);
cur = now / NORM;
cur += 1970;

/* ローカライズタイム
if(cur == y)
*/
h += zg;

if(h >= 24){
h %= 24;
}

wday = temp;
if(wday > 6){
wday %= 7;
}

/* 第一引数構造体へ代入 */
tm->tm_sec = s;
tm->tm_min = mn;
tm->tm_hour = h;
tm->tm_mday = (int)d;
tm->tm_mon = month;
tm->tm_year = c;
tm->tm_wday = wday;
tm->tm_yday = (int)days;
tm->tm_isdst = 0;

/* 成功 */
return(TRUE);
}

int main()
{
time_t t;
struct tm tm;
static unsigned char *date[] = {"日", "月", "火", "水", "木", "金", "土" };
unsigned char buf[7];
char *Z;

time(&t);

localtime_s(&tm, t);

Z = (char *)getenv("TZ");

memset(buf, 0x00, sizeof(buf));
strncpy(buf, Z, 3);

printf("%d年", tm.tm_year);
printf("%02d月", tm.tm_mon);
printf("%02d日 ", tm.tm_mday);
printf("(%s) ", date[tm.tm_wday]);
printf("%02d時", tm.tm_hour);
printf("%02d分", tm.tm_min);
printf("%02d秒", tm.tm_sec);
printf(" %d日目 ", tm.tm_yday);

printf("%d ", tm.tm_isdst);
printf("%s\n", buf);

return(0);
}



[ メッセージ編集済み 編集者: コブラ 編集日時 2005-12-26 20:43 ]
未記入
大ベテラン
会議室デビュー日: 2005/03/12
投稿数: 148
投稿日時: 2005-08-24 21:15
コブラちゃんがこんなことを書くなんて。
面倒なので中身のコードは見てません。

// 第一引数: *tm
// 型: tm 構造体へのポインター

そりゃポインターだってことはわかるよ。

// 出力 : *tm
// 型: tm 構造体へのポインター

だからなにがでてくるんだよそれ。

// 戻り値 : 論理型
// TRUE : 1
// FALSE: 0

そんなことは知ってるよ。
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2005-08-30 12:36
これまでの経緯をちゃんと把握してから書き込みましょう。

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