C言語で関数ポインタ型で呼び出し。malloc領域から関数ポインタ型で呼び出しについて。

このQ&Aのポイント
  • C言語で関数ポインタ型による関数の呼び出しを勉強中です。
  • print()関数は受け取った文字列を表示するだけの関数です。main内の(*mem)関数ポインタを用いて実行しました。
  • mallocで確保した領域にmemcpyでprint()をコピーし、(*mem)で実行していますが、WindowsとUNIXで動作が異なるようです。
回答を見る
  • ベストアンサー

malloc領域から関数ポインタ型で呼び出し

現在C言語で,関数ポインタ型による関数の呼び出しを勉強しています. print()という関数は受け取った文字列を表示するだけの関数ですが, これを通常の関数ポインタを用いて実行したのがmain内の(*mem)("FROM POINTER.");です. これに対して後半は,mallocで確保した領域にmemcpyでprint()をコピーし, (*mem)("FROM COPIED AREA.");で実行しています. 実行環境は以下の通りです. OS:Windows7 Personal 32bit CPU:Intel Core i5 M430 統合開発環境:Visual C++ 2010 Express Edition コンパイルには成功しています. 実行すると"FROM POINTER."は表示されるのですが, "FROM COPIED AREA."は表示されず,プログラムが動作を停止してしまいます. mem = (void (*)(char*))tmp;までは実行できているようですが, (*mem)("FROM COPIED AREA.");の実行の時点で停止しているようです. またコマンドプロンプトからBorland C++ 5.5.1でコンパイルしても, 同じように動作が停止してしまいます. ただし同じプログラムでも,Cygwinからgccでコンパイルすると, 意図した通りの挙動となっていることを確認しています. コンパイラの違いによるものなのか, UNIXとWindowsの違いによるものなのか判断できずにいます. (もしくはライブラリのmalloc,memcpy辺りの実装法の違いでしょうか?) どなたかご存知でしょうか. ----------------------------------------- #include<stdio.h> #include<stdlib.h> #include<string.h> #define SIZE 512 void print(char* str){ printf("%s\n", str); } int main(int argc, char** argv){ void (*mem)(char*); void *tmp; mem = &print; (*mem)("FROM POINTER."); tmp = malloc(SIZE); memcpy(tmp, print, SIZE); mem = (void (*)(char*))tmp; (*mem)("FROM COPIED AREA."); }

質問者が選んだベストアンサー

  • ベストアンサー
  • Wr5
  • ベストアンサー率53% (2177/4070)
回答No.1

ライブラリの違い…でしょうね。 ちなみに、セキュリティを考慮するとヒープ領域に置いたコードを実行するのは好ましくありません。 # DEP(データ実行防止)の設定によっては動作させようとするとOSに殺されます。 # Windows7なら…[システムのプロパティ]->「詳細設定」タブ->パフォーマンスの[設定]ボタン->「データ実行防止」タブで設定。 また、コード領域にあるものをヒープ領域にただコピーしただけで問題がないか…はライブラリによるでしょう。 今回は別のライブラリ(printf())をコールするだけでしたから、環境によっては問題にならないかも知れませんが、 コピーしたマシン語でアドレスが異なってしまう場合などに吹っ飛びかねません。 # 実行ファイルやDLLはメモリに読み込まれた時点でアドレス再配置が行われますので大丈夫ですが、メモリ上に配置が終わったものの場合は再配置は動作しませんし。

HisanaEXE
質問者

お礼

ハードウェアでもDEPはサポートされており,例外を除いてDEPが有効になっていました.コンパイルしたexeファイルを例外に追加しようとしたところ,このプログラムはDEPを有効にして実行する必要があります,と言われてしまいました.そのため重要なプログラムとサービスのみに有効にする設定にして,再起動してから実行してみました.結果は変わらなかったので,DEPが作動して停止したがコマンドプロンプトにその情報が伝わっていないか,OSが動く前にライブラリが原因で動作が停止したのかと思います. セキュリティが堅めだと言われているUNIX系ではこのような書き方が問題ないのに,Windowsでは禁止されているというのは少し不思議です.近い将来,UNIX系でも機械語の挿入によるウイルス実行が流行れば似たような保護機能を付けるかもしれませんね. 確かにプログラムの配置位置と参照するメモリの位置の関係も,考慮しないといけませんね.本来データ用に後から確保された領域を,命令用に使用するといった手法を用いるウイルスがどうやってこの問題を解決しているのか少し興味がわきました.(私は決して作りません!) 詳しくありがとうございました.

その他の回答 (4)

  • wormhole
  • ベストアンサー率28% (1620/5655)
回答No.5

>セキュリティが堅めだと言われているUNIX系ではこのような書き方が問題ないのに,Windowsでは禁止されているというのは少し不思議です.近い将来,UNIX系でも機械語の挿入によるウイルス実行が流行れば似たような保護機能を付けるかもしれませんね. もしかしてcygwinでそうだったからUNIX系はそのようになってると思ってらっしゃいますか? cygwinはライブラリレベルでUNIXをエミュレートしてるだけでUNIXではありませんが。 たしかエミュレートするために色々小細工してたと思いますよ。 UNIX系はmallocの実装にもよりますけどmmapを利用しているのなら、その領域は実行不可に設定されてると思いますし。

HisanaEXE
質問者

お礼

>cygwinはライブラリレベルでUNIXをエミュレートしてるだけでUNIXではありません 細部は色々と違うということですか. No.4の方もテストして下さっていますが,ディストリビューションごとにも違うようです. UNIXだから,という発想からいったん離れて確認してみます. ありがとうございました.

  • ki073
  • ベストアンサー率77% (491/634)
回答No.4

Linux(Scientific linux 6)とOSX(10.7)のgccでコンパイルしてみましたが、コンパイラはwarningも出ませんね。正常に終了しました。 実行させると両方ともエラーで落ちます。gdbで確認しましたところ OSXは(*mem)("FROM COPIED AREA."); でheap領域に飛ぼうとしたときにbus errotで Linuxはmemcpy(tmp, print, SIZE); で落ちます。こちらはなぜ落ちるかはわかりませんが、両方とも実行はできませんでした。 むしろCygwinのメモリ管理の方が問題では。

HisanaEXE
質問者

お礼

OSごとに落ちる部分が違って面白いですね. 許可している/禁止しているの違いなのか, OSが想定している/していないの違いなのか,気になりますね. Cygwinではなぜ可能なのかということも調べてみます.

  • wormhole
  • ベストアンサー率28% (1620/5655)
回答No.3

最低限コンパイラが生成したコードがリロケータブルでないと動きませんよ。 他にもヒープ領域で実行できるようになってるかなどもありますけど。

  • kmee
  • ベストアンサー率55% (1857/3366)
回答No.2

少なくとも、print関数のコンパイル後のサイズがSIZEバイト以上あったら、全部をコピーできてませんよね。

HisanaEXE
質問者

お礼

SIZEが小さいと,Cygwinでも動かなくなるのは確認しました.けれど,たとえば50000くらいにしてもWindowsでは実行できないので,別の問題かと思います. 回答ありがとうございました.

関連するQ&A

  • ダブルポインタ?

    下記のような関数が存在し、 最終的には mainで宣言した変数 "a" にDataGetでコピーしたデータを mainで再び使用したいのですが、 下記の方法だとmainに何も帰ってきません。 (NULLのまま・・・) 下記の関数を使用しmainに上手くデータを 引き継ぐためにはどうすれば良いのでしょうか? void Mem(int nSize,void **ptr){ char *tmp; tmp = malloc(nSize); *ptr = tmp; } void DataGet(char *aa,char *a){ int nSize = 5; (void)Mem(nSize,(void **)&a); memcpy(a,aa,nSize); return; } void main(){ char aa[20]; char *a = 0x00; memset(aa,0x00,20); memcpy(aa,"test",4); (void)DataGet(aa,a); /* aデータをここから再び使いたい */ }

  • mallocについて

    mallocで得るアドレスの使い方が分からなくて困っています。 void main(){     char *mem;     char *initial_address; /*初期アドレス保持*/     mem = (char*)malloc(5);     printf("malloc mem -> %p\n", mem);     initial_address = mem;     mem = "abcd";     printf("%c - %p\n", *mem, mem);     printf("%c - %p\n", *initial_address, initial_address); } なぜかmem = "abcd";を実行するとmemのアドレスが変わってしまいます。 mallocで得た5byteはmem = "abcd";実行後のアドレスから5byteということでしょうか。。 それとmem = 'a';を実行するとmemのアドレスはなぜか変わりません。 どうか、ご教授よろしくお願いします。m(__)m

  • ポインタのポインタの初期化法

     文字列をポインタを使って扱うとき、例えば、初期化は次のように行えますよね。 #include <stdlib.h> char *s; s = (char *)malloc(1000); (これに続いてscanf("%s", s);など)  これと同様にして、二次元の配列を、ポインタのポインタを使って表したいとき、 char **s; と宣言したものを、malloc()関数を使って初期化することはできるのでしょうか。よろしくお願いします。

  • 関数内で設定したポインタ値について

    main(){ char *bbb = NULL; datachk(bbb); } void datachk(char *ccc){ ccc = (char *)malloc(6); memset(ccc, NULL, 6); memcpy(ccc, "12345", 5); } 上記のソースですが、mainでCallしている datachk(bbb); この部分bbbに void datachk(char *ccc) ここで設定した値を入れたいです。 datachkこの関数内では入るのですが、戻るとだめです。 mainで渡しているアドレスと、 datachkで設定しているアドレスが違うのはわかるのですが、 どのようにするのが1番宜しいのでしょうか。 どなたかご教授ください。

  • void型へのポインタ

    というのがC言語にありますよね? このvoid型へのポインタというのは、 どのようにイメージすればいいのでしょうか? 例えばchar型へのポインタなら、 指している領域は 1バイトの領域ですよね? ではvoid型は? また malloc関数を 使って char *p; p=(char *)malloc(1000); とするとでchar型にキャストしているから、 1個1バイト分の領域が1000個用意して、 先頭アドレスをpに格納するのですよね? では、 int *q; q=(int *)malloc(1000); としたら、用意されるのは、int型にキャストしているから 1個2バイト分の領域が500個用意されるのでしょうか? お願いします。

  • malloc関数の使い方について(初心者)

    膨大なデータ数を扱うためにmalloc関数を用いて配列にデータを格納し、それを表示するプログラムを考えました。コンパイルはできるのですが、実行するとエラーが起こります。freeの開放の仕方が間違っているのでしょうか?よろしくお願いします。 /*ソース*/ #include <stdio.h> #include <stdlib.h> #define N 130000 int main(void) { double *p; int i; p = malloc(N); if(!p){ printf("割り当てエラー"); exit(1); } for(i=0;i<N;i++){ p[i]=i; printf("%f\n",p[i]); } free(p); }

  • 関数へのポインタ

    超初心者です。 C言語を使ってsin波を生成して音を鳴らそうとしているのですが・・ネットで調べてもよくワカリマセン・・ 超初心者な私でも理解できるようなサイトを教えて下さい. また、関数へのポインタも勉強しているのですが, char *(*func)(void); といったchar 型へのポインタを返す関数へのポインタというのがあったとして,関数へのポインタは理解できたのですがさらにchar 型へのポインタとなると一体これが何を指しているのかさっぱりで・・・・ ご協力お願いします.

  • 配列ポインタの関数中のメモリ領域

    C初心者です。 関数中で配列ポインタを宣言する場合についての質問です。 たとえばDouble型の2次元のローカルな配列ポインタを用いる場合、 その配列要素数が100である場合は void 関数名(引数1,引数2,...){ int i; double *a[2]; for(i=0;i<2;i++){ a[i] = (double*)malloc(100*sizeof(double)); } for(i=0;i<2;i++){ free(a[i]); } } またこの値を引数1とする場合、引数1をoutとすると void 関数名(double *out,....) とし、 for(i=0;i<2;i++){ out[i] = a[i]; } とすればよいのでしょうか? もしこれがあっているとすると、つぎのような現象で困っています。 配列要素数を50000個ぐらいとし、複数の関数で、同様に mallocを用いて、配列ポインタのローカルでメモリ領域を確保しようとした場合、コンパイルは成功するのですが、その後実行すると、エラーが発生したというメッセージとともにコマンドウィンドが強制終了します。 コンパイラはVisual C++ EXpress Edition 2008です。 データサイズを小さくすると、エラーは起きません。 malloc関数で確保するメモリサイズは、関数の入力引数で定義された変数を用いて計算しており、データサイズに応じて変更されます。 よろしくお願いいたします。

  • 関数ポインタについて

    C言語によるUNIXシステムにプログラミング入門という本を読みながらC言語を勉強しています。 しかし、サンプルとして提示された下記の内容の意味がわかりません。 分からない箇所が「関数ポインタ」と呼ばれるものがついているということが分かった程度で、どういう意図で記述されているのかがわかりません。 分からないプログラムの処理内容は、ファイル内のデータを16進数で表示するというものです。 分からない箇所を記します。 #include <stdio.h> #define BUFF 17 /*buffer*/ #define ERR -1 /*system call error*/ void usage(void); /*put usage message*/ char *command_name /*command name*/ FILE *fpin; /*file pointer*/ main(int argc,char *argv[ ]){ char *rindex(const char*s,int c); /*末尾から文字列検索*/ void hexdump(void); ... ... } void hexdump(void){ ... ... } void usage(){ ... ... } 不明なのは、main関数の中の char *rindex(const char*s,int c); /*末尾から文字列検索*/ void hexdump(void); です。 Cについて、不明なところが多いので、利用する関数は使う前に宣言しなければいけない程度の理解ですが、そうだとしてもusageメソッドはmain関数の外であるのに、rindexとhexdumpは何故main関数の中で宣言されているのでしょうか。 上記の不明点とは別で、rindexの前にポインタが付いていると思うのですが、hexdumpやusageにはついていません。 知人からは、関数までのポインタを返すとのことでしたが、用途もいまいち理解できません。 全てではなくてもいいので、ヒントをいただけるとうれしいです。 よろしくお願いします。

  • メモリ操作関数『malloc(),free()』

    /*10バイトのメモリ領域を確保し、その領域に文字列"Allocate"を代入せよ。*/ /*ただし、確保した領域は、プログラム終了までに開放すること。*/ #include<stdio.h> #include<stdlib.h> void main(void) { char *ptr; ptr = (char *)malloc(10); printf("Allocate\n"); free(ptr); } 今、ライブラリ関数を勉強しています。 この問題をとりあえず作ってみて、実行も成功したのですが、10バイトのメモリ確保の数値を変えても、何も変わらないため本当に問題の要求どおりのプログラムが作れているのか謎です。 間違っているなどのアドバイス宜しくお願いします。

専門家に質問してみよう