質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

5回答

16133閲覧

callocで確保した領域を越えてメモリにアクセスできてしまう

退会済みユーザー

退会済みユーザー

総合スコア0

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2016/06/26 17:50

編集2016/06/28 13:37

###前提・実現したいこと
calloc使用時に確保したつもりの領域を越えてメモリにアクセスしてしまいます。
確保した領域を越えてメモリにアクセスできてしまう原因をお教えください。

###発生した問題について
c言語でメモリを動的に確保する方法を確認するためにpaiza.ioにて下記のソースコードを実行しました。

calloc関数を使用して構造体のポインタ変数を5個分確保したつもりだったのですが10個分確保できているかのように振るまいます。
確保したつもりの範囲を超えたメモリにアクセスしているように見えます。
確保したメモリをfree関数で開放した際には実行時エラーを出してくれますがそれ以前には何もエラーを出さないようです。

確保した分の範囲を越えてメモリにアクセスしてしまう原因についてお教えいただけないでしょうか?
情報源だけでも結構ですのでどうかよろしくお願いいたします。

###該当のソースコード

C

1#include <stdio.h> 2#include <stdlib.h> 3 4typedef struct _xy{ 5 int x; 6 int y; 7}xy_t; 8 9int main(void){ 10 size_t n = 5; 11 size_t m = 10; 12 13 xy_t *xy = NULL; 14 xy = (xy_t *)calloc(n,sizeof(xy_t)); 15 if(NULL == xy){ 16 printf("false\n"); 17 } 18 19 for(size_t i=0;i<m;i++){ 20 xy_t *target = &xy[i]; 21 target->x = i; 22 target->y = i * 2; 23 } 24 for(size_t i=0;i<m;i++){ 25 xy_t *target = &xy[i]; 26 printf("%d %d\n",target->x,target->y); 27 } 28 29 free(xy); 30 xy=NULL; 31 32 return 0; 33 34 35} 36

###出力
0 0
1 2
2 4
3 6
4 8
5 10
6 12
7 14
8 16
9 18

###paiza.ioでの実行時エラー

*** Error in `./Main': free(): invalid next size (fast): 0x000000000150f010 ***

###試したこと
paiza.ioの他にideoneのgcc-5.1とclang 3.7で実行しても同じ出力がでます。
実行時エラーは無し。
webで公開されている情報を調べましたが確保したメモリの二重開放についてぐらいしか見つけられませんでした。
###解決後追記
###実験 配列宣言や動的確保をしなくても基準となるアドレスを渡せば再現できるか?
###実験結果
再現できた。
###実験 ソースコード

c

1#include <stdio.h> 2#include <stdlib.h> 3 4typedef struct _xy{ 5 int x; 6 int y; 7}xy_t; 8 9int main(void){ 10 11 size_t m = 10; 12 13 xy_t origin; 14 xy_t *xy = &origin; 15 16 for(size_t i=0;i<m;i++){ 17 xy[i].x = i; 18 xy[i].y = i * 2; 19 } 20 21 for(size_t i=0;i<m;i++){ 22 printf("%d %d\n",xy[i].x,xy[i].y); 23 } 24 25 return 0; 26 27} 28

###実験 出力
0 0
1 2
2 4
3 6
4 8
5 10
6 12
7 14
8 16
9 18

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

C言語はメモリ操作に対するチェックが非常に緩く、ほぼノーチェックで書き込んでしまいます。これは、極限まで処理速度を追求したい場合やメモリマップドI/Oなどの特殊な操作に必要なので、あえてそうしている面もあります。

また、calloc(mallocも同様)は、ヒープ領域と呼ばれる、あらかじめOSからまとまったサイズで確保済みの領域から、必要な分だけ切り出して使うような仕組みになっていて、実際には引数で指定したサイズよりも大きなメモリ領域が存在します。

そのため、ご質問のようにcallocで確保した領域を越えた部分にもメモリは存在していて、一見何事もなく動いているように見えてしまいます(配列変数にも同じことが言えます)。ただし、書き込んだ部分に他のデータが書かれていた場合そのデータを壊すことになり、運が良ければ何らかの異常が発生してバグっていることに気づきますが、運が悪ければバグっていることに気づかず、出荷(納品)した後で発覚するという悲劇が訪れます。

C言語では領域の範囲外にはアクセスしないように意識して設計しないといけません。それを支援するためのツールやフレームワークなども存在します。

C言語は難易度が高い、と言われる理由の一つです。

投稿2016/06/26 21:29

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/06/28 13:53

catsforepaw様 ご回答ありがとうございます。 確保したつもりでは無くともメモリが存在していれば無理やりにアクセスできてしまうこともあるのですね。 Cは理解しづらかったり今回みたいな不具合に遭遇して混乱することもありますが、理解できる範囲に関してであれば何をやっているのか把握しやすいところは好きです。
guest

0

ベストアンサー

xyについてn(5)個分しかメモリを確保していませんが、for文でiを0からm(10)未満まで回しているので、確保していない、6〜10個分目のメモリのところ(xy[i]xy[9]まで増える)までアクセスしています。これは理解できていますよね?

C言語では、アクセスしようとしている領域が正しく確保されたメモリ領域なのかは、コンパイルや実行時に見ていません。アクセスしようとしている領域が正しく確保した領域であることを保証することはプログラマーの仕事であり、コンパイラや実行環境の仕事ではありません。確保したメモリより外の領域にアクセスした場合、何が起きるかは未定義(何が起こるかわからない)です。

このようなアクセスがいわゆる「バッファーオーバーフロー」というものです。エラーになる場合もあれば、そのまま動いてしまう場合もあります(ただし、うまく動作した場合も偶然に過ぎず、他で思わぬバグが出る場合があります)。どうなるかはコンパイラやそのオプション、実行時の環境によって異なります。また、このようなイレギュラーな処理を検知するような仕組みを別途入れる場合もあります。

これはcalloc/malloc等で動的に確保した領域だけではなく、ローカル変数の配列などでも発生します。また、freeした後にアクセスなども状況によってはそのまま動く場合がありますし、適当なアドレスに無理矢理アクセスだって可能な場合があります。しかし、バグや脆弱性の原因になりますので、動くからと言っても絶対にしてはいけないことの一つです

今回はfreeの時に正常ではないアクセスが以前あったことがたまたま検知できたため、エラーになっただけになります。処理系(コンパイラ)によってはそのまま完了してしまうこともあり得ます。

投稿2016/06/26 21:21

編集2016/06/26 21:22
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/06/28 13:43

raccy様 ご回答ありがとうございます。 cではメモリを自分で管理しなければならないと聞いたことはありました。 しかし、確保したメモリを自分で解放しなければならない程度のことだと思っていました。 アクセスする際にも気をつけないと今回みたいな不具合が起きるのですね。 どうもありがとうございました。
guest

0

規格の話じゃなくて、実装の話(実際にはどうなるか)を書きます。
プログラムは、OSからメモリをページ単位(例えば4096バイト)で割り当てを受けます。
割り当てを受けていないメモリアドレスに対してアクセスを行うと、ページ例外とか、セグメンテーション違反などのOSの例外が発生し、普通はプログラムがエラーで終了します。

割り当てを受けたページをどう使うかは、プログラムに任されています。Cの実行時サブルーチンは、OSから割り当てを受けたメモリを細分化して、mallocやcallocでユーザープログラムに渡しますが、その範囲を越えてのアクセスを普通はチェックしません(規格では未定義動作なので他の方が書いているようにチェックする処理系もあります)。OSは、プログラムに割り当てたページの範囲で何をしようがお構いなしなので、OSレベルの例外やエラーにはなりません。

malloc/callocされた範囲を越えた読み出しは、ページサイズを越えてOSから割り当てられていないメモリアドレス領域まで行かない限りは、普通に成功します。そのアドレスにたまたま入っていた値が読まれるだけ。大幅に越えてOSから未割り当ての領域に行くと上記の例外が発生します。

書き込みについては、ページサイズを越えない場合でも、別の変数内容や実行時ルーチンの管理エリアを破壊します。運が良ければ全くの未使用領域で影響なし。今回の場合は、malloc/calloc/freeで使う管理領域を破壊したため、freeの時にエラーになりました。

投稿2016/06/27 00:10

otn

総合スコア84645

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/06/28 14:35

otn様 ご回答ありがとうございます。 malloc系の関数でヒープ領域のアドレスが取得できてしまうのでそこを起点としてアクセスするとヒープ領域の範囲内であれば問題が無いかのように動作してしまうのですね。 freeの時のエラーに関してはideoneや自分の環境のclangでは発生しないのでコンパイラやオプションしだいのようですね。
guest

0

C言語はそういうものです。
今回のコードではたまたまエラーが出ませんでしたが、運が悪ければエラーで落ちます。
(コーディングミスが分かるので、運が良いとも言えますが)
アクセスする領域の安全性は完全に使う側に委ねられます。
そこがC言語の難しさの一つでもあります。
逆にシビアなコーディングが求められるで、初心者が最初に学ぶのにも良い言語だと思います。

投稿2016/06/27 00:01

ttyp03

総合スコア16998

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/06/28 14:19

ttyp03様 ご回答ありがとうございます。 今回見つけるまで気にしてもいなかったので発見できたのは運が良かったと思います。
guest

0

適切な回答がありますので、
こんな処理系もあるよという例

産総研のFail-Safe C のサイト紹介
Fail-Safe C: 安全なC言語コンパイラ
https://www.rcis.aist.go.jp/project/FailSafeC-ja.html
研究自体は一部継続中な様子↓
Fail-Safe C: Top Page
https://staff.aist.go.jp/y.oiwa/FailSafeC/index-ja.html
Fail-Safe C は、メモリ安全性を保証する ANSI C 言語のフルスペックの実装です。 ANSI C 言語の仕様で定められた全てのメモリ操作(キャストや共用体を含む)に対し その安全性を保証し、全ての危険なメモリアクセスを事前に検知し防止します。

過去には、インタプリタタイプのCも製品として存在した事もありましたが、
主流とはなりませんでした。

投稿2016/06/26 23:02

daive

総合スコア2030

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/06/28 14:15

daive様 情報ありがとうございます。 概要だけざっくりと見ましたが絶対にオーバーフローさせないコンパイラという感じですね。
daive

2016/06/28 22:58 編集

時間のある時に、↑の処理系だけでなく、 Cの処理系の実装や、ライブラリの実装を、診てみて下さい。 自身で使用している処理系のソースは手に入らなくても、 GNU-C /C++ はソース公開です。 マイコン系のC / C++ のライブラリ いきなり現代的処理系では、手に負えなければ、 UNIX SYSTEM V とコンパイラ BDS-Cとそのライブラリ関数:簡単です。 Borland C++ Sweet:昔のBorlandのアプリパックが手に入るのであれば、            稀にネットオークションに出ます。 CP/M や DOS用の処理系などで、ネットに有る物を探してみたり。 ’ Cは、自分用にライブラリをカスタマイズ可能な処理系です。 PC向けでは行わなくなりましたが、 マイコン用では、必要に応じて変更したりします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問