構造体のメンバのメモリ配置について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
parapara

構造体のメンバのメモリ配置について

#1

投稿記事 by parapara » 18年前

独学で勉強してる者ですが、又分からない事が出てきており、困っております。
下記の状態(ソースに質問書いてある)なんですが,ご助言お願いします。
#include<stdio.h>         //共用体で86系レジスタを表現する
#include<stdlib.h>
#include<string.h>
struct hregs{
	unsigned char al;     //ここでalとahは隣接することを宣言してるみたいなんですが、
	unsigned char ah;     //構造体のメンバ同士って必ず隣接する物なんでしょうか?
};
typedef union regs{
	struct hregs h;
	unsigned short int ax;
}Regs;
int main(void){
	Regs reg;
	reg.h.ah=0x1A;
	reg.h.al=0x2B;
	printf("ah:%X\n",reg.h.ah);
	printf("al:%X\n",reg.h.al);
	printf("ax:%hX\n",reg.ax);

	return(EXIT_SUCCESS);
}

box

Re:構造体のメンバのメモリ配置について

#2

投稿記事 by box » 18年前

> 	unsigned char al;     //ここでalとahは隣接することを宣言してるみたいなんですが、
> 	unsigned char ah;     //構造体のメンバ同士って必ず隣接する物なんでしょうか?

この場合は、すぐ隣同士のアドレスに配置されます。
一方、

#include <stdio.h>

typedef struct __STRUCT {
	char c;
	int n;
} STRUCT;

int main(void)
{
	STRUCT s;
	
	printf("s.cのアドレス:%p\n", &s.c);
	printf("s.nのアドレス:%p\n", &s.n);
	return 0;
}

このプログラムを実行すると、結果は例えば次のようになります。

【実行結果の例】
s.cのアドレス:0012FF84
s.nのアドレス:0012FF88

s.nのアドレスは、0012FF85であってもよさそうに思えますが、
実際はそうなりません。
これは、メンバー配置の際に「境界合わせ(alignment)」を行なっているからです。
私のところでは、メンバーの開始アドレスが必ず4の倍数になるように
境界を調整しています。「4バイトバウンダリー(4 bytes boundary)」
あるいは「ワードバウンダリー(word boundary)」といいます。
この例では、0012FF85~0012FF87の3バイトは、未使用のままとなります。
このような、未使用領域のことを「パディング(padding)」といいます。
CPUによっては、4バイトではなく8バイトバウンダリー(ダブルワードバウンダリー)の
場合もあります。

なお、s.cとs.nとの間に別のメンバーの領域があるわけではないので、
隣接しているといえなくもないです。
「隣接」をどう定義するかによると思います。

parapara

Re:構造体のメンバのメモリ配置について

#3

投稿記事 by parapara » 18年前

1バイトと1バイトだと、境界越えしないために隙間なく隣接する。
1バイトと4バイトだと境界越えしてしまう為、
次の記憶領域に配置されパディングが発生するという事ですね?
毎回ありがとうございます。疑問が氷解しスッキリしました。

一応まだ質問にしときます。これで合ってるでしょうか?<!--1

ケン@ポチ

文字列bを文字列aにコピーする関数の作成

#4

投稿記事 by ケン@ポチ » 18年前

おはようございます。

文字列bを文字列aにコピーする関数の作成という学校の昔の課題を復習しています。

この頃はポインタと聞くだけで頭が混乱する状態でしたので、ほぼ友達にやってもらっていました。。。
現在春休み中ですのでポインタの復習がてらに挑戦しています。


以下が作成したプログラムなのですが、コンパイルの段階でエラー文を吐かれるのでちゃんと起動するかも分りません。
エラーの内容はbasic1.c 3: ) が必要
       basic1.c 19: ) が必要
というものなのですが、ちゃんと)はついていますし・・・

#include<stdio.h>

int str_copy(*p1,*p2);

int main(void){
char a[256],b[256];
char *p1,*p2;

p1= a;
p2= b;

gets(p2);

str_copy(p1,p2);

return 0;
}

int str_copy(*p1,*p2){
int c=0;
while(*p2 != '\0'){
*p1 = *p2;
p1++;
p2++;
c++;
}
printf("%s\n",a);
printf("length=%d\n",c);
return c;
}

ちなみにこの課題ではなにがなんでもgetsを使えということでしたのでgetsを使用しています。

ケン@ポチ

Re:文字列bを文字列aにコピーする関数の作成

#5

投稿記事 by ケン@ポチ » 18年前

肝心の質問を書き忘れていました。

質問はエラーの解決方法を教えてくださいというものです。

なお、プログラム自体が自分の意図した物と違うかもしれないのですが、それは実際に自分で確認したいので、
間違っていても秘密にしておいてください。

もしどうしても手に負えなければ追加質問をします。

miyaza

Re:文字列bを文字列aにコピーする関数の作成

#6

投稿記事 by miyaza » 18年前

int str_copy(*p1,*p2);
*p1と*p2の型がありません。プロトタイプ宣言と関数本体。

int str_copy();の内部にaが存在しないためprintf()でエラー。

Yuki

Re:文字列bを文字列aにコピーする関数の作成

#7

投稿記事 by Yuki » 18年前


>エラーの内容はbasic1.c 3: ) が必要
>       basic1.c 19: ) が必要
>というものなのですが、ちゃんと)はついていますし・・・


3行目、19行目を確認するというところはOKです。

ですが、必ずしも)がついていないためのエラーとは限りませんので、
該当行の構文をよ~~く見直してみてください。

↓ヒント隠しておくので、マウスで選択すると見えます。

ヒント:関数の引数の型は何ですか?

組木紙織

Re:文字列bを文字列aにコピーする関数の作成

#8

投稿記事 by 組木紙織 » 18年前

charを忘れていますね。
修正箇所にはコメントを入れてあります。

関数の宣言、定義の部分には引数には<em>引数の型</em>を明示的に教えてあげる必要があります。
#include<stdio.h> 

int str_copy(char  *p1, char *p2);//修正箇所 

int main(void){ 
char a[256],b[256]; 
char *p1,*p2; 

p1= a; 
p2= b; 

gets(p2); 

str_copy(p1,p2); 

return 0; 
} 

int str_copy(char *p1,char *p2){ //修正箇所
int c=0; 
while(*p2 != '\0'){ 
*p1 = *p2; 
p1++; 
p2++; 
c++; 
} 
printf("%s\n",a); //このaの意図が不明
printf("length=%d\n",c); 
return c; 
}

ケン@ポチ

Re:文字列bを文字列aにコピーする関数の作成

#9

投稿記事 by ケン@ポチ » 18年前

miyazaさんありがとうございました!!

char *p1,char *p2と書き直し、aの部分をp1にする事でちゃんと?表示されました。

ただ文字列と文字列の長さはちゃんと表示されるのですが、間に変な文字が出てきます。
例えばThis is a penと打つと、
This is a pen
怖N 怖
length=13
といった感じになります。これの原因は分かりますでしょうか?

ケン@ポチ

Re:文字列bを文字列aにコピーする関数の作成

#10

投稿記事 by ケン@ポチ » 18年前

書き込んでいる間にコメントが。
Yukiさん、組木紙織さんありがとうございました。型を忘れるなんて、ポインタ云々の前にちゃんと覚えておかなきゃ駄目ですよね^^;

printf("%s\n",a); //このaの意図が不明とのことですが、これはaをp1に変更する事で、無事表示させる事ができました。

Yuki

Re:文字列bを文字列aにコピーする関数の作成

#11

投稿記事 by Yuki » 18年前


while(*p2 != '\0'){
*p1 = *p2;
p1++;
p2++;
c++;
}


この部分でp1のポインタは"This is a pen"の1番最後に移動してます。
先頭から表示させたいのであれば、先頭のアドレスをよそに退避しておく必要があります。

ケン@ポチ

Re:文字列bを文字列aにコピーする関数の作成

#12

投稿記事 by ケン@ポチ » 18年前

int str_copy(char *p1,char *p2){
int c=0;
while(*p2 != '\0'){
*p1 = *p2;
p1++;
p2++;
c++;
}
while(p1>p2){
p1--;
}
printf("%s\n",p1);
printf("length=%d\n",c);
return c;
}

よそに退避の仕方が分からなかったのでこの様に書き直した結果、変な文字は表示されなくなり自分が意図したものになりました!

ありがとうございました!

なぎ

Re:文字列bを文字列aにコピーする関数の作成

#13

投稿記事 by なぎ » 18年前

while(p1>p2){
p1--;
}

このコードは、非常に問題のあるコードです。
また、もしも、なんだかわからないけれど、適当に書き直したら動いてしまった……という行動は、今後、非常に困ったことになります。
このままの行動をとり続けると、将来原因不明のバグを量産することになります。

例えば、上記で、「意図したものになりました」というプログラムの、先頭を、

char a[256],b[256];
から、
char b[256],a[256];
に入れ替えて、何が起こるか確認してみてください。
そして、なぜ、意図しない結果になるかも考えてみると良いと思います。

さて、この場合は、あえて書くなら

while(p1>p2){
p1--;
}

よりも、

p1 = p2;

です。

Yuki

Re:文字列bを文字列aにコピーする関数の作成

#14

投稿記事 by Yuki » 18年前

よその退避方法です。
int str_copy(char *p1,char *p2){ 
	int c=0;
	char *p1_wk;

	p1_wk = p1;
	while(*p2 != '\0'){ 
		*p1 = *p2; 
		p1++; 
		p2++; 
		c++; 
	} 
	while(p1>p2){ 
		p1--; 
	} 

	p1 = p1_wk;

	printf("%s\n",p1); 
	printf("length=%d\n",c); 
	return c; 
}
退避方法の例であって、プログラムの完成品ではありません。
実行してみると、改善箇所が見えてくると思います。

ケン@ポチ

Re:文字列bを文字列aにコピーする関数の作成

#15

投稿記事 by ケン@ポチ » 18年前

gets(p2)でp2の先頭アドレスが返され、それをstr_copy(char *p1,char *p2)に放り込む。
すると関数内で\0までbからaに文字がコピーされる。
アドレスはp1p2共に終端まで行っている。
先頭から表示させたいのでp1のアドレスを先頭に戻したい。
ならばp2とp1は同じアドレスだからp1>p2で先頭に戻そう。

といった考えの下に
while(p1>p2){
p1--;
}
というソースを書きました。
ですが、こうやって説明しようと頭で考えているうちに気づいたんですが、p1とp2って同じアドレスじゃないですよね。
aのアドレスを0番地とするとbは258番地になるのかな?だからchar a[256],b[256];の場合はp2の方が大きくて・・・するとp1>p2自体もおかしくなってきますね。

p1=p2ということは、p2のアドレスをp1のアドレスに代入するから両方とも同じアドレスになるということですよね?てことはこの場合p2++でp2のアドレスは最後まで行っちゃってるからp1にもp2の最後のアドレスが入るということでしょうか。それともgetsでとってきたp2の先頭アドレスが入るのでしょうか?

後者ならばp1=p2の書き方は理解できますが、前者ならばなぜ先頭に戻さなくてもいいのか分りません。。

ケン@ポチ

Re:文字列bを文字列aにコピーする関数の作成

#16

投稿記事 by ケン@ポチ » 18年前

なぎさんとYukiさんのヒントを下に以下のように書き直してみました。



#include<stdio.h>

int word_copy(char *p1,char *p2);

int main(void){
char a[256],b[256];


gets(b);

word_copy(a,b);

return 0;
}

int word_copy(char *p1,char *p2){
int c=0;
char *p3;

p3 = p2;
while(*p2 !='\0'){
*p1 = *p2;
p1++;
p2++;
c++;
}
p1 = p3;
printf("%s\n",p1);
printf("length=%d\n",c);
return c;
}

p3にp2の先頭アドレスを代入しておいて、最後にp1にp3のアドレスを代入しました。これによりp2の先頭アドレスがp1に代入され表示もされるようになりました!!

って書いててまた疑問が。

p1にp2の先頭アドレスを代入したら文字が表示されるのは当然だと思うのです。だってp2とp1は同じアドレスを指しているのだからp2の中身とp1の中身も同じはずですよね。
ということはこれはコピーされたわけじゃなく、p2そのものを表示したにすぎないのでしょうか?

なぎ

Re:文字列bを文字列aにコピーする関数の作成

#17

投稿記事 by なぎ » 18年前

失礼しました。

p1 = p2;
でもだめですね。
実は、これ単純に、
while(p1>p2)
{
p1--;
}
がうまく動いた時の結果を書いただけなのですね。
最初、p1 > p2 が成立していたのなら、p1 を戻して、p1 == p2 になったところで止まりますから、安全な下記かとしては、
p1 = p2;
これで、p1, p2 の最初の関係によらず、同じ結果になります。

というわけで、ちゃんと動いたように見えたのは、見間違いだったのかなと。

なぎ

Re:文字列bを文字列aにコピーする関数の作成

#18

投稿記事 by なぎ » 18年前

> ということはこれはコピーされたわけじゃなく、p2そのものを表示したにすぎないのでしょうか?

そうです。
ちゃんとコピーされたかどうかを確認するためには、呼び出し側で、
a と b をそれぞれ
printf("a = %s\n", a);
こんな感じで表示してみる必要があります。

または、(保存もとの)p2 ではなくて、(保存先の)p1 を表示すればOKです。

ケン@ポチ

Re:文字列bを文字列aにコピーする関数の作成

#19

投稿記事 by ケン@ポチ » 18年前

呼び出し側にprintf("a = %s\n", a); を書き加えた所ちゃんとa=this is a pen と表示されました!!この事からコピー成功といえます。本当にありがとうございました!!

Yuki

Re:文字列bを文字列aにコピーする関数の作成

#20

投稿記事 by Yuki » 18年前

もうひとつのスレでなぎさんが指摘されていますが、
コピーした後かならず'\0'(文字列の終端)を代入するようにしてください。

while(*p2 !='\0'){
*p1 = *p2;
p1++;
p2++;
c++;
}

*p1 = '\0';


"たまたま"動くと放置されやすいバグです。

閉鎖

“C言語何でも質問掲示板” へ戻る