'Work'에 해당되는 글 7

  1. 2007/11/20 자원의 한계
  2. 2007/11/20 Visual Studio의 fflush 이야기 (1)
  3. 2007/03/29 TimeAttack
  4. 2006/05/18 Buffering Error
  5. 2006/03/31 Builder Listview Column Sorting
  6. 2006/03/31 Variant Conversion
  7. 2006/03/30 "extern C"에 대하여
Work | Posted by 흑목 2007/11/20 14:16

자원의 한계

http://dv.ajou.ac.kr/bbs/view.php?id=comp_2007_fall&page=2&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=headnum&desc=asc&no=254

모든 프로그램은 실행이 되면 프로세스라는 것으로 바뀌죠. 운영체제는 모든 프로세스가 사용할 수 있는 자원을 제한합니다. 여기서 자원이라 함은, 파일, 메모리, 사운드, 디스크, 마우스, 프로그램이 사용할 수 있는 모든 것을 의미하죠.

Windows도 예외가 아닙니다. 프로세스가 사용할 수 있는 최대 자원을 제한합니다. 여기에는 한 프로세스가 동시에 열 수 있는 파일의 개수도 포함됩니다. 감이 잡히시죠? 제가 기억하기로 특별한 설정을 하지 않는 이상 Windows에서 하나의 프로세스가 동시에 열 수 있는 파일의 개수는 512개로 알고 있습니다.여기에 표준 입력(stdin), 출력(stdout), 에러(stderr) 스트림도 포함이 됩니다. 따라서 실제 동시에 열 수 있는 파일의 개수는 509개가 되겠죠.

예제 프로그램의 루프에서 fopen으로 연 파일을 닫지 않고 계속 같은 파일을 열게 되는데 이것은 같은 파일이라고 하더라도, 프로세스의 입장에서는 동시에 사용하고 있는 파일의 개수가 늘어나게 되는 것을 의미합니다. 따라서 509번 열게 되면 자원의 한계에 도달하여 오픈 에러가 나는 것입니다. (이런 표준 입력 함수가 에러난 이유를 알고 싶으면 errno라는 것을 인터넷에서 찾아보시기 바랍니다.)

위와 같은 이유 때문에 실습시간에 항상 다 사용한 파일은 fclose를 통해서 닫아주라고 하는 것이며, 같은 맥락으로 동적 할당한 메모리를 다 사용하였으면 반드시 free를 해주라는 말을 하는 것입니다. 이는 모두 프로세스가 실행중에 사용할 수 있는 자원이 제한되어 있기 때문입니다.

PS. 위에 대한 내용은 정컴 전공 과목인 시스템 소프트웨어 과목을 들으면 알 수 있는 내용이며, Advanced Programming in the Unix environment 책을 보면 좀더 심화하여 공부 할 수 있습니다.

'Work' 카테고리의 다른 글

자원의 한계  (0) 2007/11/20
Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
Work | Posted by 흑목 2007/11/20 14:00

Visual Studio의 fflush 이야기

컴프 숙제를 하면서 사용자의 입력을 받는 interactive한 프로그램을 짜는데 이 때, scanf를 많이 사용하죠. 이 때 입력 오류 처리를 하기 위해서 fflush 함수를 많이 사용하는데, 이에 대해서 얘기를 해보려 합니다.

실제 프로그래밍에서 flush란 개념은 출력 버퍼에 남아 있는 출력해야 할 것들을 모두 출력하여(혹은 그냥 버려서) 출력 버퍼를 비운다는 개념입니다. 그런데 학생들이 Windows의 visual studio 환경에서 프로그램을 개발하면서, 다음과 같이 입력 버퍼를 비워버리곤 합니다.

   fflush(stdin);

Visual studio에서는 위의 코드를 사용하면 표준 입력 버퍼의 내용을 모두 비우긴 합니다만(MS에서는 flush의 개념을 확장하여 사용하긴 합니다.), 가끔 윈도우즈의 다른 개발환경(MS사가 아닌 다른 회사의 개발환경이나, 다른 언어, 또는 Windows가 아닌 다른 운영체제)에서는 위의 코드가 우리가 원하는 바, 즉 표준 입력 버퍼의 데이터를 비우는 작업을 수행하지 않는 경우가 있습니다.

사실, C 표준에서는 위의 code를 수행 했을 때 나타나는 결과는 정의되어 있지 않다(undefined). 라고 말하고 있으며, 대부분의 다른 언어에서도 이를 정의하고 있지 않거나, 무시하고 있습니다. 이 사실을 인지하고 있지 않는 많은 학생들이 VS에서 주로 사용하던 저 코드를 다른 환경에서 사용한 뒤 나타나는 프로그램 에러에 대해 적절한 대처를 하지 못하는 경우를 많이 보았습니다. 그러니깐 프로그램 에러가 나는 이유는 결국 다른 환경에서는 fflush(stdin); 코드가 제대로 수행되지 않기 때문이다. 라는 거죠.

결론은,
1. 프로그래밍에서 flush란 개념은 출력 버퍼를 대상으로 한 개념이다
2. 입력 버퍼를 flush하는 것은 정의되어 있지 않는 모호한 행동이며, 다른 개발 환경과 호환성이 없다.
입니다.

앞으로 컴프를 패스하게 되면 다른 여러 환경에서 프로그램을 짜게 될텐데, 도움이 될까 해서 몇자 적어보았습니다.

'Work' 카테고리의 다른 글

자원의 한계  (0) 2007/11/20
Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
Work | Posted by 흑목 2007/03/29 17:59

TimeAttack

VI로 코딩하느라 무려 20분이나 걸렸다....

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define NAME_MAX 256

typedef struct _student
{
 char name[NAME_MAX];
 int id;
 int course_count;
 int course_time;
} STUDENT;

int main( void )
{
int student_count;
STUDENT *stdarray;
int i_for;
int course_max = 0;
int totaltime = 0;
int j_for;
STUDENT tempStudent;
FILE *fp;

printf( "학생 수를 입력하세요. : " );
scanf( "%d", &student_count );
fflush( stdin );

/* 학생 수 입력 */
if( student_count <= 0 )
{
 printf( "학생 수를 잘못 입력하였습니다." );
 return -1;
}

/* 학생 수 배열 설정 */
stdarray = ( STUDENT * )malloc( sizeof( STUDENT ) * student_count );

if( stdarray == NULL )
{
 printf( "시스템 에러입니다." );
 return -1;
}

/* 학생 정보 입력 시작 */
for( i_for = 0; i_for < student_count; i_for ++ )
{
 printf( "학생%d의 이름: ", i_for + 1 );
 fgets( stdarray[i_for].name, NAME_MAX, stdin );

 /* 개행 제거 */
 stdarray[i_for].name[strlen(stdarray[i_for].name) - 1] = '\0';
 
 printf( "학생%d의 학번: ", i_for + 1 );
 scanf( "%d", &stdarray[i_for].id );
 fflush( stdin );

 printf( "학생%d의 수강 과목 수: ", i_for + 1 );
 scanf( "%d", &stdarray[i_for].course_count );
 fflush( stdin );
 
 printf( "학생%d의 수강 과목 시간: ", i_for + 1 );
 scanf( "%d", &stdarray[i_for].course_time );
 fflush( stdin );
}

/*  학생 정보 입력 끝 */

/* 신청 과목 수가 가장 많은 학생 찾기 시작 */
for( i_for = 0; i_for < student_count; i_for ++ )
{
 if( course_max <= stdarray[i_for].course_count )
   course_max = stdarray[i_for].course_count;
}

/* 신청 과목 수가 가장 많은 학생 찾기 끝, 화면 출력 */
printf( "==신청 과목 수가 가장 많은 학생==\n" );
for( i_for = 0; i_for < student_count; i_for ++ )
{
 if( stdarray[i_for].course_count == course_max )
 {
  printf( "%s, %d, 과목수: %d, 과목당 시간: %d\n",
   stdarray[i_for].name, stdarray[i_for].id,
   stdarray[i_for].course_count, stdarray[i_for].course_time );
 }
}

/* 총 수강 시간 구하기 */
for( i_for = 0; i_for < student_count; i_for++ )
{
 totaltime += stdarray[i_for].course_count * stdarray[i_for].course_time;
}
printf( "==모든 학생들의 총 수강 시간 = %d\n", totaltime );

/* 파일 출력을 위한 내림차순 정렬 시작 */
for( i_for = 0; i_for < student_count; i_for++ )
{
 for( j_for = i_for + 1; j_for < student_count; j_for++ )
 {
  if( stdarray[i_for].course_count * stdarray[i_for].course_time <
   stdarray[j_for].course_count * stdarray[j_for].course_time )
  {
   /* swap */
   strcpy( tempStudent.name, stdarray[i_for].name );
   tempStudent.id = stdarray[i_for].id;
   tempStudent.course_count = stdarray[i_for].course_count;
   tempStudent.course_time = stdarray[i_for].course_time;

   strcpy( stdarray[i_for].name, stdarray[j_for].name );
   stdarray[i_for].id = stdarray[j_for].id;
   stdarray[i_for].course_count = stdarray[j_for].course_count;
   stdarray[i_for].course_time = stdarray[j_for].course_time;

   strcpy( stdarray[j_for].name, tempStudent.name );
   stdarray[j_for].id = tempStudent.id;
   stdarray[j_for].course_count = tempStudent.course_count;
   stdarray[j_for].course_time = tempStudent.course_time;
  }
 }
}

/* 파일 출력 시작 */
fp = fopen( "out.txt", "w" );

if( fp == NULL )
{
 printf( "출력 파일 열기 에러.\n" );
 return -1;
}

for( i_for = 0; i_for < student_count; i_for++ )
{
 fprintf( fp, "%s, %d, 과목수: %d, 과목당 시간: %d\n",
  stdarray[i_for].name, stdarray[i_for].id, stdarray[i_for].course_count,
  stdarray[i_for].course_time );
}

fclose( fp );

/* 배열 해제 */
free( stdarray );

return 0;
}

'Work' 카테고리의 다른 글

자원의 한계  (0) 2007/11/20
Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
Work | Posted by 흑목 2006/05/18 15:11

Buffering Error

컴프 게시판 답변

int main(void)
{
       FILE *fp;
       char *file="test.txt";
       char c;

       fp=fopen(file,"wt+");
       fprintf(fp,"haha");
       rewind(fp);
       
       while(1)
       {
               c=getc(fp);
               if (c==EOF) break;
               printf("%c ",c);
       }
       fclose(fp);


       return 0;
}

라는 코드를 컴파일 해보면 test.txt 파일에 haha라는 값이 저장됩니다.

그런데, rewind(fp); 부분을 쓰지 않으면 haha뒤로 쓰레기 값이 저장됩니다.

fprintf 뒤로는 파일에 영향을 줄만한 함수가 없다고 생각되는데 쓰레기 값이 저장되는 이유가 뭔가요?

getc하는 과정에서 생기는 쓰레기 값 같은데 아무리 생각해봐도 이해가 되질 않습니다. (이 함수는 읽어오는 함수이기때문에..)


그리고 위의 소스에서 wb를 하거나 wt를 했을때 결과값에 차이가 없는데

바이너리 모드와 텍스트 모드의 아웃풋 에서의 차이점은 무엇인가요


-----------------------------------------------------------------------------------

위의 코드를 봤을 때 다음과 같이 생각하는 것이 일반적입니다.

1. file에 4byte를 write를 하였음.
2. offset이 4byte 이동. offset은 file의 끝.
3. read를 함. file의 끝이므로 EOF를 받고 종료


아래의 답변은 지극히 저의 상상임을 밝혀둡니다.

standard I/O library는 file에 대해서는 fully bufferd를 합니다.

buffer가 꽉 차기 전까지는 write를 하지 않습니다.

fprintf를 call 했을 때 haha의 4byte 쓰기를 요쳥했지만 call할 당시엔 바로 쓰지 않습니다.
하지만 file의 offset은 증가합니다.

그리고 아래에서 1byte씩 read를 수행합니다. read를 해도 offset은 계속 증가하겠죠.

read를 할 때 EOF를 반환하면 멈추게 코드가 작성 되있는데 돌려보면 논리적으로 봤을 때는 파일의 끝이므로 EOF가 반환되어야 하지만 EOF가 반환되지 않고 어느정도 돌다가 우연히 EOF가 반환되어 멈추는 것을 알 수 있습니다. 여기서 우리는 EOF가 파일의 끝이라고 항상 return이 되지는 않음을 알 수 있습니다.(실제 EOF가 어떤 상황에서 return되는지는 library code를 봐야 합니다.)

위의 code에서는 read를 4092번하고 빠져나옵니다.

프로그램이 종료 된 후 test.txt 파일을 읽어보면 4096 byte가 써져 있음을 알 수 있습니다.

이 4096byte는 우리가 처음에 출력한 4byte "haha"와 4092번 read한 것과 관련이 있는것 같습니다.

여기서 우리는 하나의 가설을 새울 수 있습니다.

'buffering하고 있던 data를 write할 때에는 변한 offset만큼 write한다.'

검증을 위해 아래의 code를 돌려보았습니다.

int main(void)
{
FILE *fp;
char *file="test.txt";
char c;
int a = 0;

fp=fopen(file,"wt+");
fprintf(fp, "haha");

while(1)
{
c=getc(fp);
if (c==EOF) break;

printf("%c ",c);
a++;

if( a == 20 ) break;
}

fclose(fp);

return 0;
}

결과로 4byte "haha" + 20byte 값 총 24 byte가 찍힌걸 파일에서 확인 할 수 있었습니다.

위 문제의 해결 방법은 write를 수행한 후 바로 buffer를 flush를 해 줘서 read의 offset 변환이 buffer를 flush할 때 영향을 끼치지 않게 하는 것입니다.

어떤 조건에서 EOF를 반환하는지 library를 뜯어보지 않는 이상 알 수 없으므로 파일의 끝이라고 무조건 EOF를 리턴한다고 방심하면 안됩니다. 역시나 이와 같은 결과를 예측할 수 없는 code는 작성을 안하는것이 좋겠지요.

PS. rewind 같은 경우에는 구현이 어떻게 되있는지 모르나 내부에서 flush를 하는 것을 trace를 통해 알 수 있습니다. 따라서 rewind할 경우에는 쓰래기 값이 안붙죠.

-----------------------------------------------------------------------------------

바이너리 모드와 텍스트 모드의 차이점....

텍스트 모드일 경우에는 각 운영체제의 텍스트 파일 포맷에 맞게 실제 file을 읽고 쓸 때 변환이 이루어집니다.

예를 들면 실제 C에서는 모든 개행을 \n으로 처리합니다.

그러나 windows의 경우에는 개행이 파일에서 \r\n으로 이루어 지고, Linux의 경우에는 개행이 \n으로 이루어집니다.

따라서 \n을 쓴다고 했을 때 윈도우즈의 경우에는 \r\n으로 변환되어 Linux의 경우에는 \n으로 그대로 저장됩니다.(실제 \n을 출력하는 code를 작성하고 파일의 값을 확인해 보면 알 수 있습니다.)

바이너리 모드 같은 경우에는 포맷에 따른 변환 없이 값을 그대로 읽고 쓰게 됩니다.

따라서 Windows는 모드에따른 차이가 있지만, Linux는 차이가 없습니다.

'Work' 카테고리의 다른 글

Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
"extern C"에 대하여  (0) 2006/03/30
Work | Posted by 흑목 2006/03/31 20:36

Builder Listview Column Sorting

int SortByColumn = -1;                                  //
int SortOrder = 1;                                      //

void __fastcall TForm1::ListView1ColumnClick(TObject *Sender, TListColumn *Column)
{
   if(SortByColumn == Column->Index) SortOrder *= -1;  //
   else SortOrder = 1;                                 //
   ListView1->CustomSort(NULL, Column->Index);
   SortByColumn = Column->Index;                       //
}

void __fastcall TForm1::ListView1Compare(TObject *Sender, TListItem *Item1,
     TListItem *Item2, int Data, int &Compare)
{
   if(Data == 0)
   {
       if(Item1->Caption < Item2->Caption) Compare = -1;
       else if(Item1->Caption > Item2->Caption) Compare = 1;
       else Compare = 0;
   }
   else
   {
       if(Item1->SubItems->Strings[Data-1] < Item2->SubItems->Strings[Data-1]) Compare = -1;
       else if(Item1->SubItems->Strings[Data-1] > Item2->SubItems->Strings[Data-1]) Compare = 1;
       else Compare = 0;
   }
   Compare *= SortOrder;                               //
}

'Work' 카테고리의 다른 글

Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
"extern C"에 대하여  (0) 2006/03/30
Work | Posted by 흑목 2006/03/31 12:35

Variant Conversion

MS communication control을 이용하려다 여기저기 찾아보고 해결한 것입니다.

char * 형에서 중간에 NULL 문자까지 포함시킬 수 있습니다.


참고되시기 바랍니다.


먼저 VARAIANT -> char *


       unsigned char input_buf[512] = {0};


       VARIANT var;

       ::VariantInit(&var);

       var = m_cCommReceive.GetInput();    // -> 데이타를 var 형태로 받았습니다.


       unsigned char HUGEP *pbstr;

       HRESULT hr;


       hr = SafeArrayAccessData(var.parray, (void HUGEP* FAR*)&pbstr);

       unsigned long cE = var.parray->rgsabound[0].cElements;


       memcpy( input_buf, pbstr, nCount );    // -> var 형을 char *로 멤카피

       input_buf[nCount] = 0;


       hr = SafeArrayUnaccessData(var.parray);


       SendData(input_buf, nCount);    // ->char * 형을 처리하기 위해 사용자마음대로의 함수로 보냄.



다음으로 char * -> VARIANT 인데, 다음의 소스를 참고 했습니다.

http://excel96.cafe24.com/moin.cgi/UsingVariant


   /*http://excel96.cafe24.com/moin.cgi/UsingVariant 에서 수정*/

   unsigned char* pMyOriginalArray = data;


   HRESULT hr;

  

   SAFEARRAYBOUND rgsabound[1];

   rgsabound[0].lLbound = 0;      

   rgsabound[0].cElements = nLength;   // -> nLength 는 char *형으로 넘겨받은 문자열의 길이


   SAFEARRAY FAR* pMySafeArray;

   pMySafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);     // -> VT_UI1 형이 char형을 받습니다.

  

   char* pData;

   hr = SafeArrayAccessData(  pMySafeArray, (void**)&pData);

   memcpy(pData, pMyOriginalArray, nLength);

   SafeArrayUnaccessData(pMySafeArray);

  

   VARIANT myVariant;

   myVariant.parray = pMySafeArray;

   myVariant.vt = VT_ARRAY|VT_UI1;

  

   ////////////////////////////////////

   SendOutput(myVariant);    // -> var 형을 사용자마음대로 함수로 보냄

   ////////////////////////////////////

  

   hr = SafeArrayDestroy(pMySafeArray);


'Work' 카테고리의 다른 글

Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
"extern C"에 대하여  (0) 2006/03/30
Work | Posted by 흑목 2006/03/30 11:53

"extern C"에 대하여

간단하게 아래와 같은 프로그램이 있다고 할 때
( 파일 확장자는 .cpp로 하여 CPP style로 compile 해야 합니다. )
add_1(), fadd_2(), add_3() 가 compile되어 어떻게 이름이 바뀌는지 보면
왜 extern "C" 가 필요한지 알 수 있습니다.



#include "stdio.h"

int add_1(int a, int b);
float fadd_2(int a, int b);
extern "C" int add_3(int a, int b);

int main()
{
int a = 2, b = 3;
int c;
float fc;

c = add_1(a, b);
fc = fadd_2(a, b);
c = add_3(a, b);

return 0;
}

/********* 시험삼아 여기를 막는다
int add_1(int a, int b)
{
return a + b;
}

float fadd_2(int a, int b)
{
return (float)(a + b);
}

int add_3(int a, int b)
{
return a + b;
}
********/

################### compile/link 결과 ###############################

naver.obj : error LNK2001: unresolved external symbol _add_3
naver.obj : error LNK2001: unresolved external symbol "float __cdecl fadd_2(int,int)" (?fadd_2@@YAMHH@Z)
naver.obj : error LNK2001: unresolved external symbol "int __cdecl add_1(int,int)" (?add_1@@YAHHH@Z)

compile/link 하면 위와 같이 link error가 발생합니다.

자세히 보면
add_1 -> ?add_1@@YAHHH@Z
fadd_2 -> ?fadd_2@@YAMHH@Z
add_3 -> _add_3
와 같이 compile과정에서 이름이 바뀌어집니다.
add_1(), fadd_2()는 CPP style의 decorated name(?) 으로 바뀐 것이며
add_3()는 prototype에서 extern "C" 를 주었으므로 C style로 '_'만 앞에
붙은 것이 차이점입니다.

이상의 실험에서
CPP는 argument, return value에 따라 이름을 바꾸게 하여 반드시 protype과 일치하는 function이 link되도록 합니다. argument가
틀린 경우에는 같은 이름의 function이 여러개 존재할 수 있으며 argument에 따라 적당한 function이 link됩니다.
C는 단순히 이름앞에 '_' 만 추가되므로 argument가 틀리던 return value type이 틀리던 link는 될 수 있습니다. 심하게 잘못되면
compile/link는 되는데 오동작을 할 수도 있겠지요.

이렇게 같은 이름의 function이라도 C style, CPP style로 compile되면서
이름이 바뀌므로 C에서 CPP로 작성된 function을 참조하거나 CPP에서 C로
작성된 function을 참조할 수가 없습니다.
이럴때 extern "C" 를 사용하여 CPP에서 정의/참조 되는 function 이름을 C style로 강제로 바꾸는 것입니다.
보통 *.h file에 아래와 같이 하여 *,c, *,cpp 에서 공용할 수 있는
protype 선언을 합니다.

#if defined(__cplusplus)
extern "C" {
#endif

int func1();
int func2();
.
.
.

#if defined(__cplusplus)
};

'Work' 카테고리의 다른 글

Visual Studio의 fflush 이야기  (1) 2007/11/20
TimeAttack  (0) 2007/03/29
Buffering Error  (0) 2006/05/18
Builder Listview Column Sorting  (0) 2006/03/31
Variant Conversion  (0) 2006/03/31
"extern C"에 대하여  (0) 2006/03/30