'프로그래밍/Embedded 최적화 과정'에 해당되는 글 26건

  1. 2007/03/10 게으른 엔지니어 C에서 포인터의 덧셈
  2. 2007/03/10 게으른 엔지니어 C에서 가변 인자 함수에 대하여...
  3. 2007/03/07 게으른 엔지니어 C의 가변 인자 함수를 만들때 가변 인자에 대한 얘기...
  4. 2007/03/07 게으른 엔지니어 C 코드를 어셈블리로 바꾸었을때...
  5. 2007/03/06 게으른 엔지니어 C에서 사용자 정의형 선언
  6. 2007/03/06 게으른 엔지니어 C에서 문자 배열과 포인터
  7. 2007/03/04 게으른 엔지니어 C에서 1U의 의미란?
  8. 2007/03/04 게으른 엔지니어 C에서 이름 공간의 의미
  9. 2007/03/04 게으른 엔지니어 C에서 unsigned 형에 대한 내용
  10. 2007/03/04 게으른 엔지니어 C에서 volatile과 형 변환에 관하여
int arr[10];

int *arrp = arr;

에서 배열과 포인터의 차이점은

1. arr에 다른 값을 대입할 수 없다. arrp는 다른 값을 대입 할 수 있다.

2. sizeof(arr)과 sizeof(arrp)가 서로 다른 값을 가진다.

   32 bit 시스템에서 sizeof(arr)은 4*10인 40 byte 이고 sizeof(arrp)는 4 byte이다.

 

[code type=c]#include <stdio.h>
int main(void)
{
 int arr0[7] = {0, 1, 2, 3, 4, 5, 6};
 int *arrp;
 int i;

 arrp = arr0;
 for ( i = 0; i < 7; i++ )
 {
  printf("%d %d %d %d %d\n", arrp[i], *(arrp+i), *(i+arrp), i[arrp], (i+2)[arrp-2]);
  /* []는 연산자이다. arrp[i] == *(arrp + i) == *(arrp +i*sizeof(*arrp)) 이다. 때문에 arrp[i] == i[arrp] == (i+2)[arrp-2]
   */
 }
 printf("\n");

 arrp = &arr0[3];
 printf("arrp[1]:%d\n", arrp[1]);
 printf("arrp[-2]:%d\n", arrp[-2]);
 {
  int i;
  char *cp = &i;
  short *sp = &i;
  int *ip = &i;
  printf("%x\t %x %x %x\n", &i, cp+1, sp+1, ip+1);
  /* cp + 1 == cp + 1*sizeof(*cp)
   * sp + 1 == sp + 1*sizeof(*sp)
   * ip + 1 == ip + 1*sizeof(*ip)
   */
 }

 return 0;
}[/code]

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/152

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/152

가변 인자 함수는 역 어셈 코드를 생각해 보면 간단하다.

함수를 호출하게 되면 그 함수를 위한 ebp가 새로 생긴다.

함수의 인자는 ebp 위에 생기게 된다. (물론 stack이 아래로 주소가 작아 진다는 가정, 인텔...)

 

인자 3

인자 2

인자 1

ebp

 

와 같은 형태가 되므로 첫번째 인자1 로부터 인자 수와 형태를 알게 되면 ebp로 부터 주소를 알아 낼 수 있으므로 하나씩 뽑아서 사용하면 된다.

printf 함수의 원형을 보면

printf(const char *, ...); 이다.

const char *에서 뒤에 오는 인자의 수와 형태를 문자열로 부터 뽑아 낼 수 있으므로 간단하게 생각할 수 있다.

 

[code type=c]#include <stdarg.h> // 가변 인자 함수를 사용하기 위한 헤더 파일
#include <stdio.h>

int plus_func(int, ...); /* 가변 인자 함수 선언 방법. 뒤에 ... 임. 첫번째 인자는 가변이 안됨. 무조건 고정 인자가 와야 함. 즉 int plus_func(...)는 불가능 함. 첫번째 인자는 인자의 갯수와 데이타 형을 알려 줘야 함. */
int max_func(int, ...);

int main(void)
{
 int x = 10, y = 20;

 printf("%d\n", plus_func(2, 3, 2));
 printf("%d\n", plus_func(3, x, y, 3));
 printf("%d\n", max_func(3, x, y, 3));

 return 0;
}

int plus_func(int count, ...)
{
 int i;
 int sum = 0;
 va_list list; /* va_list는 가변 인자 함수 사용시 사용하는 매크로이다. */
 
 va_start(list, count); /* va_list로 정의한 변수 list를 첫번째 인자로 하고 뒤에 인자는 가변 인자 함수의 첫번째 인자를 받는 변수를 적어야 함. 여기서는 count로 받는다. 인자를 하나씩 빼오겠다는 표현이다. */
 for ( i = 0; i < count; i++ )
 {
  sum += va_arg(list, int);
 }
 va_end(list);

 return sum;
}

/**
 * 가변 인자 함수 사용하기
 * 가변 인자로 주어진 값 중에서 가장 큰 값을 넘겨 준다.
 */

int max_func(int count, ...)
{
 int i, num;
 int max = 0;
 va_list list;
 
 va_start(list, count);
 max = va_arg(list, int);
 for ( i = 0; i < count - 1; i++ )
 {
  num = va_arg(list, int); // va_arg(list, int)는 실행될때마다 값을 빼온다.

/*

  if ( max < va_arg(list, int) )

  {

      max = va_arg(list, int);

  }

   와 같이 하면 이상한 결과가 나온다.

*/
  if ( max < num )
  {
   max = num;
  }
 }
 va_end(list);

 return max;
}[/code]


 

실행결과

5

33

20

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/151

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/151

가변 인자 함수에서 인자를 받을때 (컴파일러가 진급을 시킨다)

기본인자 진급을 적용한다.

1. 정수 진급 : 정수형태는 int -> unsigned int 로 변경 된다. int 형으로 담을수 없는 수이면 unsigned int로 바꾼다.

2. 실수 진급 : float -> double 형으로 변경

 

가변인자

1. one pass 방식인 C의 컴파일러인 경우 가변 인자 함수의 경우 인자의 데이타 형을 알려 주지 않기 때문에 컴파일러가 가장 크다고 생각되는 데이타 형으로 변경을 해야 무사히 컴파일이 가능하다.

2. 실제 가변 인자 함수 작성시 인자의 데이타 형이 줄어들기 때문에 프로그램 하기가 쉬워 진다.

3. 실제 값을 그대로 받게 만들수도 있지만, 최초에 이런식으로 만들어 졌기 때문에 하위 호환성(역사적 이유) 때문에 그대로 사용하고 있다.

 

[code type=c]#include <stdio.h>
int main(void)
{
 signed char sc;
 signed short ss;

 sc = 10; /* 00001010 이 8 bit 10이며 32 bit에도 똑같은 값이다. */
 ss = 10;
 printf("%x %x\n", sc, ss);

 sc = -10;

/* 00001010의 1의 보수는 11110101에 1을 더하면 2의 보수 이므로11110110 이다. 이 수를 int로 진급 시킨다. signed char는 int로 다 포함 할수 있다.8 bit수가 32 bit int로 바뀌어야 한다. 바꾸는 방법은 11110110 앞에 1을 24개 붙인다. 왜냐하면 1110110의 1의 보수는 1001 이고, 이것의 보수가 1이 24개 앞에 있는 1110110 이다. 간단하게 생각하기 위해서는 -10을 바로 32 bit 수로 쓴다고 생각해보자. 음수인 경우 앞에 전부 1을 붙여 준다. */
 ss = -10;
 printf("%x %x\n", sc, ss);

 {
  float f = 123.625;
  printf("%f\n", f);
 }

 return 0;
}[/code]

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/150

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/150

C 코드를 어셈블리로 만들어서 보는 방법은 생각보다 간단하다. Visual C++ 6.0의 경우 셋팅만 바꾸면 바로 어셈블 코드를 볼 수 있으며, 주석으로 C 코드까지 포함시켜서 볼 수 있다. 다른 컴파일러의 예를 들기 위해서 MinGW gcc 3.2.3으로도 만들어 보았다.

Microsoft Visual C++ 6.0에서 만든 코드에서 46번째 줄에서 52까지를 필요한 stack을 전부 oxc로 채우는 것을 알 수 있다. 이 부분은 어떻게 보면 하나의 오버헤드이다. Visual C++ 6.0 컴파일러에 존재하는 것이다. 다른 버전의 컴파일러는 확인을 하지 못해서 단정 짖지 못한다.
 

아래 코드에서 DWORD는 4 byte를 말한다.

원본 C code

[code type=c]int main(void)
{
 int a = 100;
 int b = 200;
 int c;

 c = a + b;

 return 0;
}[/code]


Assemble code generated by MinGW gcc version 3.2.3
[code type=asm] .file "1520.c"
 .def ___main; .scl 2; .type 32; .endef
 .text
.globl _main
 .def _main; .scl 2; .type 32; .endef
_main:
 pushl %ebp
 movl %esp, %ebp
 subl $24, %esp
 andl $-16, %esp
 movl $0, %eax
 movl %eax, -16(%ebp)
 movl -16(%ebp), %eax
 call __alloca
 call ___main
 movl $100, -4(%ebp)
 movl $200, -8(%ebp)
 movl -8(%ebp), %eax
 addl -4(%ebp), %eax
 movl %eax, -12(%ebp)
 movl $0, %eax
 leave
 ret[/code]

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

Assemble code generated by Visual C++ 6.0


 [code type=asm] TITLE D:\cipher\0524\1520\1520.c
 .386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
_DATA SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA ENDS
CONST SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST ENDS
_BSS SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS ENDS
$$SYMBOLS SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS ENDS
$$TYPES SEGMENT BYTE USE32 'DEBTYP'
$$TYPES ENDS
_TLS SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS ENDS
; COMDAT _main
_TEXT SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT ENDS
FLAT GROUP _DATA, CONST, _BSS
 ASSUME CS: FLAT, DS: FLAT, SS: FLAT
endif
PUBLIC _main
; COMDAT _main
_TEXT SEGMENT
_a$ = -4
_b$ = -8
_c$ = -12
_main PROC NEAR     ; COMDAT

; 2    : {

 push ebp
 mov ebp, esp
 sub esp, 76     ; 0000004cH
 push ebx
 push esi
 push edi

 

; 아래는 Visual C++에만 존재하는 stack 영역을 초기화 하는 부분이다.
 lea edi, DWORD PTR [ebp-76]
 mov ecx, 19     ; 00000013H
 mov eax, -858993460    ; ccccccccH
 rep stosd

; stack 영역을 초기화 하는 부분 끝

; 3    :  int a = 100;

 mov DWORD PTR _a$[ebp], 100   ; 00000064H

; 4    :  int b = 200;

 mov DWORD PTR _b$[ebp], 200   ; 000000c8H

; 5    :  int c;
; 6    :
; 7    :  c = a + b;

 mov eax, DWORD PTR _a$[ebp]
 add eax, DWORD PTR _b$[ebp]
 mov DWORD PTR _c$[ebp], eax

; 8    :
; 9    :  return 0;

 xor eax, eax

; 10   : }

 pop edi
 pop esi
 pop ebx
 mov esp, ebp
 pop ebp
 ret 0
_main ENDP
_TEXT ENDS
END[/code]

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/149

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/149

C에서 사용자 정의형 선언은 typedef 를 이용해서 하게 된다. 구질 구질한 설명보다 아래 예제 코드를 보자.

[code type=c]#include <stdio.h>

typedef int INT; // 자체는 선언이므로 여러개 중복되도 상관은 없다.
INT a; // int a;

int main(void)
{
 typedef int ar_t[10][20]; // 통용 범위는 선언된 이후 main 함수 내부에서만 사용 가능.
 ar_t ar; // int ar[10][20];
 ar[5][3] = 100;
 {
  typedef int br_t[20];
  br_t k[10];
 }

 return 0;
}
[/code]

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/148

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/148

문자 배열과 포인터에 대해서 아래 코드로 한번 더 확인해 보자


[code type=c]#include <stdio.h>

void print_string(unsigned char *s)
{
 printf("print_string(): %x %s\n", s, s);
}

 

// ASCII 코드 이외에도 0~25 사이에 숫자를 넣으면 맞는 알파벳 넘기는 함수 작성

#define code(x)   "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[x]

 

int main(void)
{
 char str0[] = "ABCDEFG";
 char *str1 = "HIKLMNO";
 char c = "ABC"[2]; //char c,  char x[] = "ABC" and c = x[2] 와 같은 의미
 printf("%s %s\n", str0, str1);
 printf("%x\n", "ABC");
 print_string("Hello World!\n");
 printf("%c\n", c);
 printf("%c %c %c\n", code(0), code(3), code(5));
 printf("%s\n", "PQRSTUV" + 3);
 printf("%c\n", *("PQRSTUV" + 5) );

 return 0;
}[/code]


실행 결과

ABCDEFG HIKLMNO

40131c

print_string(): 401324 Hello World!

 

C

A D F

STUV

U
크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/147

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/147

접미사가 붙거나 붙지 않은 숫자에서 크기를 고려하는 순서

8/16진수에서 long int 보다 unsigned int를 먼저 고려하는 것은 8/16 진수를 음수로 쓰는 경우는 거의 없기 때문이다. 만약 크기가 맞으면 거기서 바로 정지하게 된다.

즉 숫자 1 을 10진수로 쓰면 처음 int 형태이므로 int로 되게 된다.

하지만, 1U를 쓰면 초기에 unsigned int를 고려한다.

xxxxxxxxxxxxxxxxU가 unsigned int의 크기로 고려할 수 없을 경우 unsigned long int로 고려하게 된다.

 

             10진수                            8진수 16진수

             int                                           int

             long int                                unsigned int

            unsigned long int                    long int

                                                        unsigned long int

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

U,u        unsigned int                               옆과 동

             unsigned long int

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

L, l         long int                                      옆과 동

            unsigned long int

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

ul        unsinged long int                            옆과 동

 

 

~, &, |  는 unsigned형에 값을 보장한다.

>>, << 는 unsigned형에 값을 보장한다. 또는 signed형 양수값에 보장한다.

위의 둘은 모두 unsigned형에만 사용하는 것이 좋다.

 

int a = 0x4fffffff

a << 1 이면 정확한 값을 주지 않는다. 왜냐하면 4를 2진수로 보면 0100이다. 이를 1 bit를 왼쪽으로 shift하면 양수가 갑자기 음수로 가게 된다.

 

여기서 문제는

unsigned int x = 0x0102030f 를 맨 마지막 bit만 clear 하는 방법을 고안하라이다.

16 bit 시스템임을 아는 경우에는 당연히 0xfffe 와 & 연산을 하면 되지만, 32 bit인 경우는 맞지 않다.

때문에 이럴때는 x & (~1U)를 하면 뒤에 붙은 U가 숫자를 unsigned int 형태로 바꾸어 주므로 16 bit 이든 32 bit 이든 상관 없이 항상 마지막 bit를 clear 해 준다. Linux kernel에서도 상당히 많이 볼 수 있는 방식이다.

 

이 사실을 알기 전에 나는 공용체를 이용하여 이를 구현하였다.

공용체에서 bit field를 원하는 만큼 사용하게 할 수 있으므로 가능하기는 하지만, 1U등을 쓰는 것이 훨씬 프로그램 코드를 간단하게 하는 방식이며, 아래와 같이 구현할 경우 만약 unsigned long int 이면 이를 위한 구조체를 또 하나 만들어야 하므로 좋지 않은 방식이다.

 

[code type=c]#include <stdio.h>

union _bitField
{
 unsigned int value;
 unsigned int x:sizeof(unsigned int) -1;
 unsigned int y:1;
};

int main(void)
{
 
 union _bitField test;
 unsigned int x = 0x0102030f;
 test.value = x;
 test.y = 0x0 & test.y;
 printf("%d %d\n", x, test.value);

 return 0;
}[/code]


크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/146

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/146

C언어의 이름공간

1. 레이블명

2. 구조체, 공용체, 열거의 태그명

3. 구초체, 공용체의 필드명

4. 그 밖의 명칭(변수 명칭 포함)

 

위의 같은 이름 공간 내에서 같은 이름은 존재 할 수 없다.

 

[code type=c]#include <stdio.h>

struct abc
{
 int x;
 int y;
 int abc;
} abc = { 1, 2, 3};

int main(void)
{
 struct abc abc = { 10, 20, 30 }; /* struct 다음에 있는 abc는 태그명이며, 그 뒤의 abc는 변수명인 그 밖의 명칭이다. */
 goto abc; // label 명
 printf("#");
abc :
 printf("%d %d %d\n", abc.x, abc.y, abc.abc);
 abc.x = 10;
 abc.y = 20;
 abc.abc = 30;
 {
  struct abc
  {
   int i;
   int j;
   int k;
  };
  printf("%d %d %d\n", abc.x, abc.y, abc.abc);
  {
   struct abc abc = { 100, 200, 300 };
   printf("%d %d %d\n", abc.i, abc.j, abc.k);
  }
 }

 return 0;
}[/code]

 

실행결과

10 20 30

10 20 30

100 200 300

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/145

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/145

1. 표준을 보면 unsigned 형은 값이 범위를 넘어서더라도 항상 같은 값을 모든 컴파일러에서 보장한다.

  1.1. 범위를 넘어설때 %(mod) 연산을 해서 값을 저장한다.

  1.2. 음수가 대입될때는

       (unsigned 형 최대값 + ㅎ)*X + Y = 대입된 값 에서 X를 -1 부터 하나씩 대입하여 Y가 unsinged 형 범위안에 들어 올때까지 계산한다.

  아래 예 : (255+1)*x + y = -300    

               x = -1 일때 y = -44

               x = -2 일때 y = 212 이므로 출력이 212가 나온다.

     (최대값 + 1)  mod (최대값 + 1) = 0을 보장해준다. (?)

2. signed형은 표준에서 책임지지 않는다.

 

[code type=c]#include <stdio.h>

int main(void)
{
 unsigned char u;
 signed char s;

 u = 300; s = 300; // 300 % (255+1) = 44 모든 시스템에서 동일한 값 보장
 printf("u = %d\ts=%d\n", u, s);

 u = -300; s = -300;
 printf("u = %d\ts=%d\n", u, s);

 return 0;
}[/code]


실행결과

u = 44    s = 44

u = 212  s = -44

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/144

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/144

메모리상에 변수 표현 방식은 IEEE754를 사용한다.

 

형 변환은 항상 값에 대해서 형변환이 일어 난다. 메모리상의 비트열을 생각하면 안된다.

 

[code type=c]#include <stdio.h>

int main(void)
{
 int i = 0x42f74000; // IEEE754에 따른 123.625의 메모리 표기법
 float f = 123.625;

 volatile int v;

 int w;

 /*
 w를 직접 메모리에서 읽고 싶다. 이 경우
 printf("%d\n", (volatile int)w)로 쓰면 아무 의미가 없다.
 아래 예제를 보면 알 수 있듯이 ( )다음에 있는 값에 대한 형변환을 하기 때문이다.
 그러므로
 printf("%d\n", *(volatile int *)&w); 와 같이 사용하면 된다.
 */

 

 printf("%f\n", (float)i);
 printf("%f\n", *(float *)&i); // 메모리에 있는 비트열을 float로 형변환

 printf("%d\n", (int)f);
 printf("%d\n", *(int *)&f); // 메모리에 있는 비트열을 int로 형변환

/* &f는 float *를 나타낸다. 이것을 int *로 변환하여 * 를 보므로 */

 return 0;
}[/code]

실행결과

1123500032.000000

123.625000

123

1123500032

크리에이티브 커먼즈 라이센스
Creative Commons License
이올린에 북마크하기(0) 이올린에 추천하기(0)
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.cipher.pe.kr/tt/cipher/rss/response/143

댓글+트랙백 ATOM :: http://www.cipher.pe.kr/tt/cipher/atom/response/143