2019/01/31

[c/c++][CERT]配列のポインタとかポインタの配列とか紛らわしい

C言語再履修シリーズ。

今日は軽めにしておこう。


ARR34-C. 式中の配列の型は適合していることを保証する


タイトルと内容に差違はなく、特にいうことはない。

私は、ポインタの配列、とか、配列のポインタ、とか、そういう紛らわしいものが苦手だから、違反コードと適合コードを真面目に見ておこう(私が書くんだったら、2次元配列ではなく1次元にして、添字で2次元を制御するだろう)。


int a[ROWS][COLS];

メモリとしては、a[row]から始まるアドレスにint型がCOLS個つながっている。
確認しておこう。

01: #include <stdio.h>
02: 
03: enum { ROWS = 10, COLS = 15 };
04: 
05: int main(void)
06: {
07:     int a[ROWS][COLS];
08: 
09:     printf("sizeof(int)=%ld\n", sizeof(int));
10:     for (int lp = 0; lp < COLS; lp++) {
11:         printf("[%d]%p\n", lp, &a[0][lp]);
12:     }
13:     return 0;
14: }

結果

sizeof(int)=4
[0]0x7fffef42ab30
[1]0x7fffef42ab34
[2]0x7fffef42ab38
[3]0x7fffef42ab3c
[4]0x7fffef42ab40
[5]0x7fffef42ab44
[6]0x7fffef42ab48
[7]0x7fffef42ab4c
[8]0x7fffef42ab50
[9]0x7fffef42ab54
[10]0x7fffef42ab58
[11]0x7fffef42ab5c
[12]0x7fffef42ab60
[13]0x7fffef42ab64
[14]0x7fffef42ab68

アドレスが4ずつずれているので、2番目の添字に対してメモリが順番に並んでいることが分かる。
だから見かけ(?)と違い、int[COLS]が[ROWS]ある、ということになる。


では、適合コードを確認しよう。

01: #include <stdio.h>
02: 
03: enum { ROWS = 10, COLS = 15 };
04: 
05: int main(void)
06: {
07:     int a[ROWS][COLS];
08:     int (*b)[COLS] = a;
09: 
10:     for (int lp2 = 0; lp2 < ROWS; lp2++) {
11:         for (int lp1 = 0; lp1 < COLS; lp1++) {
12:             a[lp2][lp1] = lp2 * 100 + lp1;
13:             //printf("[%d][%d] = %d\n", lp2, lp1, a[lp2][lp1]);
14:         }
15:     }
16:     for (int lp = 0; lp < COLS; lp++) {
17:         printf("[%2d]a=%d, b=%d\n", lp, a[5][lp], (*(b + 5))[lp]);
18:     }
19:     return 0;
20: }

結果

[ 0]a=500, b=500
[ 1]a=501, b=501
[ 2]a=502, b=502
[ 3]a=503, b=503
[ 4]a=504, b=504
[ 5]a=505, b=505
[ 6]a=506, b=506
[ 7]a=507, b=507
[ 8]a=508, b=508
[ 9]a=509, b=509
[10]a=510, b=510
[11]a=511, b=511
[12]a=512, b=512
[13]a=513, b=513
[14]a=514, b=514

1000-100の位をrow、10-1の位をcolで表した。
row=5なので、bはポインタに加算することになる。


なお、最初試したときは*bに括弧を付け忘れたので、値があわなかった。

printf("[%2d]a=%d, b=%d\n", lp, a[0][lp], *b[lp]);

とすると、

[ 0]a=0, b=0
[ 1]a=1, b=100
[ 2]a=2, b=200
[ 3]a=3, b=300
[ 4]a=4, b=400
[ 5]a=5, b=500
[ 6]a=6, b=600
[ 7]a=7, b=700
[ 8]a=8, b=800
[ 9]a=9, b=900
[10]a=10, b=-1765474560
[11]a=11, b=0
[12]a=12, b=0
[13]a=13, b=0
[14]a=14, b=-1051150057

みたいに、全然違うところを指している。
Segmentation Faultにならなかっただけ、というところか。

*b[lp]

は、

*(b + lp)

という扱いになるので、0~9までは大丈夫なのだな。


で、私が試したかったのはそういうのではなく、もうちょっと単純なことだ。

unsigned charとuint8_tは同じ型として扱ってくれるのだろうか?という疑問である。
コンパイラの認識がどうなっているのか、だな。


01: #include <stdio.h>
02: #include <stdint.h>
03: 
04: int main(void)
05: {
06:     unsigned char a[10];
07:     uint8_t *b = a;
08: 
09:     for (int lp = 0; lp < 10; lp++) {
10:         a[lp] = lp;
11:     }
12:     printf("b=%d\n", *b);
13: 
14:     return 0;
15: }

うん、gccでもg++でも、-Wや-Wallしても何も出なかった。
unsigned intみたいにすると、gccはwarning、g++はerrorだった。


やっていて気付いたのだが、longって8byte扱いなのね・・・。
てっきり、int=longで、8byteはlong longだけかと思っていた。
64bitのWSLでも、64bitのUbuntu18.04でもそうだった。

strtol()とstrtoll()で悩んだりしたけど、どっちでも同じなのか。
いっそのこと、strtod32()とかstrtod64()とかにしてほしいものだ。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。