문제의 그 코드

길이가 0인 배열이 있다. 어? 길이가 0이라...그게 말이 되나? 그 문제의 코드는 다음과 같다.

typedef struct{
    int len;
    uint8_t data[0];
}data_t;

int main()
{
    int data_len = 100;
    data_t *d = (data_t *)malloc(sizeof(data_t) + data_len);
    if(d != NULL)
    {
        d->data = data_len;
    }
    for(int i = 0; i < d->len; i++)
    {
        d->data[i] = i;
    }

    return 0;
}    

가만 보자..

일단 구조체의 멤버변수 data가 도통 이해가 되질 않는다. 내가 아는 배열 선언도 아니고..
그렇다고 포인터 선언도 아닌데.. 뭐, 굳이 어디다 끼워 맞추자면 포인터에 가깝지 않나 싶기도 하다.

스승님의 말씀에는 위 구조체의 멤버변수를 "유연한 배열 멤버" 라고 한다.

배열로 선언했지만, 마치 동적으로 할당하는 것처럼 사용할 수 있어 "유연한" 이란 단어를 사용하는 것 같다.

실제로 그렇다. 미리 길이를 정하지 않았다. 흔히 우리는 배열의 길이를 미리 정하고 사용한다.
애초에 그렇게 사용하지 않으면 안된다. 컴파일 오류가 발생하기 때문이다.

하지만 위 방법은 배열의 길이가 있다. 0이지만.. 저 0은 단순히 숫자 0이 아닌 것 같다.
뭐랄까.. 空 이라고나 할까. 비어있다. 비어있는 것이지 없는게 아니다. 내가 채워 넣는것이 곧 길이이다.

데니스 리치가 불교 신자는 아니겠지?

여튼, 위 코드에서 메모리 할당하는 라인을 살펴보자.

data_t *d = (data_t *)malloc(sizeof(data_t) + data_len);

이 코드를 보면 감이 좀 온다. 아, 구조체 사이즈만큼 할당하고, 그 뒤로 1byte의 data_len 길이를 추가로 할당하는구나.
그럼 메모리의 모양은 대충 이런 느낌이겠구나.

[------------------- 할당된 메모리 블록 (총 4 + 10 = 14 바이트) --------------------]
[   len (4 바이트)   ] [ data[0] ] [ data[1] ] [ data[2] ] ... [ data[9] ]
[     uint32_t     ] [ uint8_t ] [ uint8_t ] [ uint8_t ] ... [ uint8_t ]

상당히 메모리스럽게 접근해야 비로소 조금 마음에 와닿는다. 정말 신기하고도 재밌다.
이렇게 사용하면, 구조체 단위로 데이터를 생성하고 처리할 수 있다. 안에 배열의 데이터뿐만 아니라,
길이, 이름, 속성등 여러 attributes를 포함할 수 있어 뭔가 더 유용하게 사용할 수 있을 것 같다.

스승님 말씀

  • 길이가 0인 배열은 유연한 배열 멤버 또는 구조체 해킹 기법의 일부입니다.
  • 구조체 자체에는 data를 위한 메모리 공간이 할당되지 않습니다.
  • 주로 가변 길이 데이터를 구조체 헤더와 연속적인 메모리 블록으로 관리하기 위해 사용됩니다.
  • malloc() 등을 사용하여 구조체를 할당할 때, 필요한 데이터 크기만큼 추가적인 메모리 공간을 함께 할당하여 data 배열처럼 접근합니다.
  • sizeof(os_msg_header)data 배열의 크기를 포함하지 않습니다.

uint8_t data[0] vs uint8_t *data 차이점

특징 uint8_t data[0] uint8_t *data
메모리 할당 구조체 자체에는 데이터 공간 없음 포인터 저장 공간 할당, 데이터 공간은 별도 할당 필요
데이터 위치 구조체 뒤에 연속적인 메모리 블록으로 관리 별도의 메모리 블록에 위치 가능
할당 방식 구조체와 데이터 공간을 한 번에 malloc() 구조체와 데이터 공간을 각각 malloc()
메모리 관리 하나의 free() 호출로 전체 메모리 해제 가능 구조체와 데이터 공간을 각각 free() 해야 함
sizeof 데이터 크기 미포함 포인터 크기 포함
사용 목적 헤더와 가변 길이 데이터를 밀접하게 결합 가변 길이 데이터를 동적으로 할당 및 관리

uint8_t data[0]를 사용한 가변 길이 데이터 처리 방식

  • malloc(sizeof(structure) + data_length)를 사용하여 구조체 헤더와 가변 길이 데이터를 하나의 연속적인 메모리 블록으로 할당합니다.
  • 구조체 포인터를 통해 멤버에 접근하고, data 멤버는 구조체 헤더 바로 다음의 메모리 주소를 기준으로 배열처럼 접근하는 트릭을 사용합니다.
  • vd->data[i]vd 포인터에서 구조체 크기만큼 offset된 주소에서 i번째 바이트에 접근하는 방식으로 동작합니다.

결론: uint8_t data[0]는 구조체와 가변 길이 데이터를 효율적으로 결합하여 관리하기 위한 C 언어의 유용한 (하지만 약간은 트릭에 가까운) 기법이며, uint8_t *data는 별도의 메모리 할당을 통해 더 유연하게 가변 길이 데이터를 처리하는 표준적인 방식입니다.

'C' 카테고리의 다른 글

나만의 assert 만들기  (0) 2025.04.14
Object file..  (0) 2025.01.14
세그먼트  (0) 2023.04.13
Why is the number of array elements smaller than minus value?  (0) 2023.04.06
array length as parameter  (0) 2022.11.09

보통 디버깅은 gdb나 개발툴(IDE, SDT 등)에서 제공하는 디버거를 사용하게 된다. 하지만 때로는 단순히 출력문을 이용해서 디버깅을 진행하기도 한다. (간단한 확인 또는 디버깅 환경이 없을 경우)

 

단순히 printf("here!!\r\n"); 요렇게 쓰거나 아니면 printf("who are you -> %d\r\n", trouble_make); 요렇게 쓸 수도 있다.

하지만 약간 아쉽다. 명색이 디버깅 출력문인데, 너무 정보가 없는 것 같다.

 

좀 더 많은 정보를 포함하면서, 쓰기 쉬운 assert 함수를 만들어보자.

 

void _my_assert(bool condition,			// 조건
				 char const* message,	// 출력문
                 char const* func,		// 함수명
                 char const* file,		// 파일명
                 int line)				// 라인
{
   if (!condition)
   {
      fprintf(stderr, "Assert: %s, in %s (%s:%i)\r\n", message, func, file, line);
      exit(0);
   }
}

#define my_assert(condition, message)               \
   _my_assert(condition, message, __func__, __FILE__, __LINE__)
   
int main()
{
	int *a = calloc(1, sizeof(int));
    my_assert(a, "why u are NULL...");
    
    return 0;
}

 

음.. 위에 코드가 좀 깨지는구나. 코드 에디트 창에서는 깔끔하게 썼는데 ㅋㅋ

 

이런 코드 잘 만들어서 활용하면 유용할 듯 하다.

'C' 카테고리의 다른 글

길이가 0인 배열  (0) 2025.04.15
Object file..  (0) 2025.01.14
세그먼트  (0) 2023.04.13
Why is the number of array elements smaller than minus value?  (0) 2023.04.06
array length as parameter  (0) 2022.11.09

C 소스 파일을 컴파일하면 object 파일 (*.o) 가 생성된다. 보통은 -c 옵션을 생략하기도 하고, Makefile에서 생성되긴 하지만 별 관심없이 그냥 넘어가곤 한다. 갑자기 object 파일 안에는 무슨 내용이 있을까 궁금해져서 역시나 형님께 여쭤봤다.

먼저, 아래 캡쳐 그림은 object 파일들을 각각 다른 컴파일러로 컴파일한 내용이다. 첫번째 그림은 gcc 로 컴파일한 결과이다.

gcc

 

아래 그림은 arm-none-eabi 컴파일러로 object를 생성한 결과이다.

arm-none-eabi

 

C 소스 코드를 컴파일하여 생성된 **Object 파일(객체 파일)**에는 컴파일된 바이너리 코드와 함께 프로그램 실행에 필요한 다양한 메타데이터가 포함됩니다. Object 파일은 기계어 코드로 변환된 **섹션(section)**과 심볼(symbol) 정보를 포함하며, 이 정보는 링크 단계에서 사용됩니다.


Object 파일의 구성 요소

1. 헤더(Header)

  • Object 파일의 기본 정보를 포함.
  • 파일 형식(예: ELF, COFF, PE), 아키텍처, 엔디안, 버전 정보 등을 정의.

헤더에 포함된 정보:

  • 파일 타입: 실행 파일, 재배치 가능 파일(Object 파일), 공유 라이브러리 등.
  • 아키텍처: 타겟 CPU(예: x86, ARM).
  • 엔디안: Little-endian 또는 Big-endian.
  • 섹션 오프셋: 각 섹션의 시작 위치.

2. 섹션(Sections)

Object 파일은 여러 섹션으로 나뉘며, 각각 특정 유형의 데이터를 포함합니다.

주요 섹션:

  1. .text
    • 기계어 코드(실제 실행되는 코드)를 포함.
    • 읽기 전용.
    • 예: 함수의 기계어 명령어.
  2. .data
    • 초기화된 전역 및 정적 변수를 포함.
    • 읽기 및 쓰기 가능.
    • 예: int x = 10;
  3. .bss
    • 초기화되지 않은 전역 및 정적 변수를 포함.
    • 파일 크기를 줄이기 위해, 메모리에서만 초기화됨(값은 0으로 초기화).
    • 예: int y;
  4. .rodata
    • 읽기 전용 데이터(예: 상수, 문자열 리터럴)를 포함.
    • 예: const char *str = "Hello";
  5. .rel.text / .rela.text
    • 재배치 정보. 링크 시 동적으로 주소를 수정하기 위해 사용.
  6. .symtab
    • 심볼 테이블. 함수, 변수, 객체 등의 심볼 정보를 포함.
  7. .strtab
    • 심볼 이름을 저장하는 문자열 테이블.

3. 심볼 테이블(Symbol Table)

  • Object 파일에 정의된 함수 및 변수의 이름과 위치 정보를 포함.
  • 각 심볼에는 다음과 같은 정보가 포함됩니다:
    • 이름: 변수나 함수의 이름.
    • 섹션: 심볼이 속한 섹션(예: .text, .data).
    • 주소: 심볼의 시작 주소.
    • 유형: 함수, 변수, 재배치 심볼 등.

4. 재배치 정보(Relocation Entries)

  • Object 파일의 주소를 재배치하는 데 필요한 정보.
  • 링크 시, 다른 오브젝트 파일이나 라이브러리의 주소와 결합하여 올바른 실행 파일을 생성.

주요 항목:

  • 심볼 참조: 외부 또는 내부 심볼 참조.
  • 오프셋: 수정해야 할 메모리 주소.
  • 재배치 유형: 수정 방법(절대 주소, 상대 주소 등).

5. 디버그 정보(선택적)

  • 디버깅 도구(GDB 등)에서 사용되는 추가 정보.
  • 컴파일 시 -g 옵션을 활성화하면 생성.
  • 주요 구성:
    • 소스 코드와 바이너리 코드 간의 매핑 정보.
    • 함수 이름, 변수 이름, 파일 라인 번호 등.

Object 파일의 실제 예시

1. 컴파일

다음 C 코드를 gcc -c example.c 명령으로 컴파일하면 Object 파일이 생성됩니다.

// example.c
#include <stdio.h>

int x = 10;
const char *message = "Hello, World!";

void printMessage() {
    printf("%s\n", message);
}

2. Object 파일 분석

readelf 명령어를 사용하여 Object 파일을 분석할 수 있습니다.

헤더 정보:

$ readelf -h example.o
ELF Header:
  Class:                             ELF32
  Data:                              2's complement, little endian
  Type:                              REL (Relocatable file)
  Machine:                           ARM

섹션 정보:

$ readelf -S example.o
Section Headers:
  [Nr] Name              Type             Address          Offset
  [ 0] .text             PROGBITS         00000000         0x0000
  [ 1] .data             PROGBITS         00000000         0x0040
  [ 2] .bss              NOBITS           00000000         0x0050
  [ 3] .rodata           PROGBITS         00000000         0x0060
  [ 4] .symtab           SYMTAB           00000000         0x0080
  [ 5] .strtab           STRTAB           00000000         0x00c0

Object 파일의 링크 과정

  1. 개별 Object 파일 생성:
    • 소스 파일을 컴파일하여 각각의 Object 파일 생성.
  2. 링커(Linker):
    • 여러 Object 파일과 라이브러리를 결합하여 실행 파일 생성.
    • 재배치 정보를 사용하여 심볼을 올바르게 연결.
  3. 실행 파일 생성:
    • 최종적으로 실행 가능한 바이너리 파일 생성.

결론

Object 파일은 다음과 같은 요소로 구성됩니다:

  • 헤더: 파일 정보.
  • 섹션: .text, .data, .bss 등 프로그램 데이터.
  • 심볼 테이블: 함수와 변수의 이름 및 위치 정보.
  • 재배치 정보: 링크 시 주소 수정에 필요한 정보.

Object 파일은 컴파일된 코드와 프로그램 실행에 필요한 메타데이터를 포함하여, 링커가 실행 파일을 생성하는 데 필요한 중요한 역할을 합니다. 

'C' 카테고리의 다른 글

길이가 0인 배열  (0) 2025.04.15
나만의 assert 만들기  (0) 2025.04.14
세그먼트  (0) 2023.04.13
Why is the number of array elements smaller than minus value?  (0) 2023.04.06
array length as parameter  (0) 2022.11.09

아래 코드의 실행파일 a.out의 세그먼트를 확인해보자.

#include <stdio.h>

int main(int argc, char *argv[])
{
   printf("hello, world\n");
   return 0;
}

 

위 코드의 세그먼트는 다음과 같다.

hello, world

 

다음은 전역변수에 1000개의 값을 갖는 배열을 선언한다.

int arr[1000];

선언한 배열을 초기화 하면 배열은 bss에서 data영역으로 넘어간다.

int arr[1000] = {0x01, };

 

main 함수에 같은 길이의 배열을 선언하고 size를 출력한다.

int brr[1000];

 

이번엔 초기화도 진행해보자.

int brr[1000] = {0x01, };

 

초기화되지 않은 전역변수는 bss 세그먼트로, 초기화된 전역변수는 데이터 세그먼트로, 그리고 함수에 선언된 지역변수는 런타임에서만 세그먼트에 저장된다는 것을 알 수 있다.

'C' 카테고리의 다른 글

나만의 assert 만들기  (0) 2025.04.14
Object file..  (0) 2025.01.14
Why is the number of array elements smaller than minus value?  (0) 2023.04.06
array length as parameter  (0) 2022.11.09
parameter 실수  (1) 2022.11.08

먼저, 아래의 코드를 본다.

#include <stdio.h>

int arr[5] = {1, 2, 3, 4, 5};

#define TOTAL_ELEMENTS (sizeof(arr) / sizeof(arr[0]))

int main() {
    
    int d = -1;
    
    if(d <= TOTAL_ELEMENTS - 2)
        printf("total elements is bigger\n");
    else
        printf("total elements is smaller????\n");
    
    return 0;
}

매크로 함수에서 배열 arr의 원소 개수를 리턴한다. 그리고 조건문에서 음수와 비교한다. 원소 개수가 음수보다 작을 수는 전혀 없다.

 

하지만 결과는 신기하게도 작다.

 

이유는 이렇다. 매크로함수 TOTAL_ELEMENTS 는 unsigned int 데이터 타입을 반환한다. 그럼 조건문에서는 int 와 unsigned int 데이터 타입이 비교되고, int는 unsigned int로 승격된다.

 

결국 음수 -1을 갖는 d가 unsigned int 로 승격될 시 매우 큰 숫자로 바뀌게 된다. 그래서 결국 d가 더 크다고 판별하게 되는 것이다.

 

출처 : 컴파일러 개발자가 들려주는 C 이야기 p.32

'C' 카테고리의 다른 글

Object file..  (0) 2025.01.14
세그먼트  (0) 2023.04.13
array length as parameter  (0) 2022.11.09
parameter 실수  (1) 2022.11.08
variable arg  (1) 2022.10.03

배열을 매개변수로 받고, 해당 함수 안에서 배열의 길이를 알고자 한다.

그럼 코드는 보통 이렇게 될거다.

 

void func(int *arr)
{
	int arr_len = sizeof(arr) / sizeof(int);
}

 

언뜻 보면 맞는 것 같기도 하다. 배열의 주소를 매개변수로 받아서 해당 주소의 길이를 sizeof 로 하면 나오지 않을까..

 

안 나온다.

 

sizeof(arr) 의 값은 포인터 변수로 선언된 매개변수의 크기만 나올 뿐이다. 결국 32bit 에선 4byte, 64bit에선 8byte가 나온다.

 

배열을 매개변수로 넘길때는 반드시 해당 길이를 같이 넘겨줘라.

 

void func(int *arr, int arr_len)
{
	// codes
}

 

'C' 카테고리의 다른 글

세그먼트  (0) 2023.04.13
Why is the number of array elements smaller than minus value?  (0) 2023.04.06
parameter 실수  (1) 2022.11.08
variable arg  (1) 2022.10.03
assert  (0) 2022.08.27

먼저 작성한 코드를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
U2 ARIA_Enc_Update(IN U1 padding_flag, IN U1 *plain_text, IN U4 plain_len,  OUT U1 *cipher, OUT U4 *cipher_len)
{
    U2 ret = 0x0000;
    U4 outl = 0;
    U1 *cipher_buf = NULL;
    U4 cipher_buf_len = 0;
    int nBytesWritten = 0;
 
    ret = EVP_CIPHER_CTX_set_padding(evp_ctx_enc, padding_flag);
    if(!ret)
    {
        printf("EVP_CIPHER_CTX_set_padding ERROR\n");
        return 0xffff;
    }
 
    cipher_buf_len = plain_len + EVP_CIPHER_CTX_block_size(evp_ctx_enc);
 
    cipher_buf = (U1 *)malloc(cipher_buf_len);
    if(cipher_buf == NULL)
    {
        printf("cipher buf malloc failed\n");
        return 0xffff;
    }
 
    EVP_EncryptUpdate(evp_ctx_enc, &cipher_buf[outl], &nBytesWritten, plain_text, plain_len);
    outl += nBytesWritten;
 
    EVP_EncryptFinal(evp_ctx_enc, &cipher_buf[outl], &nBytesWritten);
    outl += nBytesWritten;
 
    //cipher = cipher_buf;
    memcpy(cipher, cipher_buf, outl);
    *cipher_len = outl;
 
 
    /*
    for(int i = 0; i < outl; i++)
        printf("%#x ", cipher_buf[i]);
        */
 
    EVP_CIPHER_CTX_free(evp_ctx_enc);
    free(cipher_buf);
 
    return 0x9000;
}
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main(void)
{
    U2 ret = 0;
    U1 key_short[16= {0x000x110x220x330x440x550x660x770x880x990xaa0xbb0xcc0xdd0xee0xff};
    U1 iv[] = { 0x0f0x020x050x030x080x050x070xaa0xbb0xcc0xda0xfb0xcc0xd00xe00xf0 };
 
    U1 plain_text_short[] = {0x000x110x220x330x440x550x660x770x880x990xaa0xbb0xcc0xdd0xee0xff,
                             0x0f0x020x050x030x080x050x070xaa0xbb0xcc0xda0xfb0xcc0xd00xe00xf0 };
 
    U1 cipher_text[100= {0x00, };
    U4 cipher_len = 0;
 
    ret = ARIA_Enc_Init(key_short, MODE_ECB, sizeof(iv), iv);
    if(ret == SUCCESS)
        printf("init success\n");
 
    ret = ARIA_Enc_Update(PADDING_BLOCK, plain_text_short, sizeof(plain_text_short),  cipher_text, &cipher_len);
    if(ret == SUCCESS)
        printf("init success\n");
    printf("cipher_len : %d\n", cipher_len);
 
    for(int i = 0; i < cipher_len; i++)
        printf("%#x ", cipher_text[i]);
    printf("\n");
    return 0;
}
cs

 

원래 생각한건 암호문이 저장된 cipher_buf 의 값을 매개변수 cipher로 저장하려고 했다. 다시 말하면 포인터로 연결하려고 했다. 문제는 이렇게 하고 cipher를 출력하면 0으로 나온다.

 

0

 

main 문에서 선언한 cipher_text 를 update문에 매개변수로 넣어 주소값을 갖게 하려는게 목적이었다. 근데 자꾸 0이 출력된다. 도대체 왜 그럴까..아...잠깐만.. 내가 무슨 짓을 하고 있는거냐..

 

첫번째, 만약 주소값을 갖게 하려면 포인터 변수를 선언해서 넣었어야 했는데.. 예를 들면 이런 느낌이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "add.h"
 
void func(int **a)
{
    int *= malloc(sizeof(int* 3);
    memset(p, 012);
 
    *= p;
}
 
int main()
{
    int *p;
 
    func(&p);
 
    printf("%d %d %d\n", p[0], p[1], p[2]);
 
 
 
    return 0;
}
cs

 

 

두번째, 배열에 넣으려면 memcpy를 했어야 했는데...

 

역시 C는 어렵다. 정진하자.

'C' 카테고리의 다른 글

Why is the number of array elements smaller than minus value?  (0) 2023.04.06
array length as parameter  (0) 2022.11.09
variable arg  (1) 2022.10.03
assert  (0) 2022.08.27
memset  (0) 2022.08.23

가변 인자.

 

인자의 갯수를 다양하게 사용할 수 있다는 뜻인것 같은데, OOP 의 오버로딩이 생각난다.

What Does Overloading Mean?

Overloading refers to the ability to use a single identifier to define multiple methods of a class that differ in their input and output parameters. Overloaded methods are generally used when they conceptually execute the same task but with a slightly different set of parameters.

 

출처 : https://www.techopedia.com/definition/3236/overloading

 

What is Overloading? - Definition from Techopedia

This definition explains the meaning of Overloading and why it matters.

www.techopedia.com

 

물론, 객체지향의 오버로딩과는 다른 듯하다. 객체지향에서는 갯수 뿐만 아니라, 타입도 다를 수 있다. 하지만 C 에서는 타입은 동일하고 매개변수의 갯수만 가변적으로 사용한다. 코드로 확인해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdarg.h> // For variable arguments
 
void showArg(int args, ...)
{
    va_list ap; // Declare pointer
    va_start(ap, args); // Set pointer and number of args
 
    for(int i = 0; i < args; i++)
    {
        int num = va_arg(ap, int); // Get arg from list of args
 
        printf("%d ", num);
    }
 
    va_end(ap); // Set pointer as NULL
 
    printf("\n");
 
}
 
int main()
{
    showArg(110);
    showArg(21020);
    showArg(3102030);
    showArg(410203040);
 
    return 0;
}
cs

 

실행화면은 다음과 같다.

executed va

 

먼저 stdarg.h 헤더파일을 포함하고, 코드를 진행한다.

그리고 가변인자 목록의 메모리 주소로 접근하기 위한 포인터를 선언한다.

 

va_list ap;

 

이 포인터 변수 ap는 매개변수 리스트에 차례로 접근하며 사용자가 매개변수를 이용할 수 있게끔 한다.

 

va_start(args, ap);

 

매개변수 갯수와 포인터 변수를 넣어 포인터 변수를 설정한다.

 

va_arg(ap, int);

 

포인터가 int 크기 (4byte) 만큼 이동하면서 해당 메모리의 값을 리턴한다.

 

va_end(ap);

 

포인터를 다시 NULL 로 초기화한다.

 

 

정수를 가변인자로 받아 출력은 문제없이 된다. 그럼 문자열도 가변인자로 받을 수 있을까? 가능하다.

아래 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdarg.h> // For variable arguments
 
void showArg(int args, ...)
{
    va_list ap; // Declare pointer
    va_start(ap, args); // Set pointer and number of args
 
    for(int i = 0; i < args; i++)
    {
        int num = va_arg(ap, int); // Get arg from list of args
 
        printf("%d ", num);
    }
 
    va_end(ap); // Set pointer as NULL
 
 
    printf("\n");
 
}
 
void showStrArg(int args, ...)
{
    va_list ap; // Declare pointer
    va_start(ap, args); // Set pointer and number of args
 
    for(int i = 0; i < args; i++)
    {
        char* str = va_arg(ap, char *); // Get arg from list of args
 
        printf("%s ", str);
    }
 
    va_end(ap); // Set pointer as NULL
 
 
    printf("\n");
 
}
 
int main()
{
    showStrArg(1"hello");
    showStrArg(2"hello"" wolrd");
    showStrArg(3"hello"" world, "":D");
 
    return 0;
}
cs

 

va_arg 함수에서 두번째 데이터 타입 매개변수를 문자 포인터형으로 입력 후, 문자열들을 매개변수로 실행하면 아래와 같이 문제없이 출력된다. 굉장히 심플한 듯 하다.

 

exec string args

 

출처 : https://dojang.io/mod/page/view.php?id=577 

 

C 언어 코딩 도장: 66.1 가변 인자 함수 만들기

66 함수에서 가변 인자 사용하기 C 언어에서 함수를 사용하다 보면 printf, scanf같이 매개변수의 개수가 정해지지 않은 함수가 있습니다. 이렇게 매번 함수에 들어가는 인수(argument)의 개수가 변하

dojang.io

 

'C' 카테고리의 다른 글

array length as parameter  (0) 2022.11.09
parameter 실수  (1) 2022.11.08
assert  (0) 2022.08.27
memset  (0) 2022.08.23
[Socket] thread  (0) 2022.06.28

이런 기능의 함수가 있었네..

 

assert(조건);

헤더파일 : assert.h

 

매개변수의 조건은 정상이어야 한고, 만약 조건과 다르다면 실행중인 프로그램을 종료해버린다.

 

잠깐, 근데 assert 가 무슨 뜻이야?

 

assert

아하, 강하게 주장하다. 프로그램을 바로 종료해버리니, 이런 단어로 함수명을 만들지 않았나 싶다.

 

그럼 어떻게 작동하나 보자.

a와 b가 있다. 그리고 두 변수의 값은 항상 달라야 한다. 그래서 assert 함수를 이용하여 만약 두 변수의 값이 같을 경우 프로그램을 종료시킨다. 이걸 함수로 표현하면 아래와 같다.

 

assert(a != b)

 

자, 그럼 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <assert.h>
#include <string.h>
 
void copy(char *dest, char *src)
{
    assert(dest != NULL); // dest is NULL, then abort
    assert(src != NULL);
 
    strcpy(dest, src);
}
 
int main()
{
    char s1[30];
    char *s2 = "hello, world";
 
    printf("%s\n", s2);
 
    copy(s1, s2);
    printf("%s\n", s1);
 
    copy(NULL, s2);
 
    return 0;
}
cs

위와 같이 코드를 짠 후, 결과값을 확인해보자.

Aborted

아하, 매개변수에 NULL이 들어가면 안되는데, NULL 값이 저장되니 assert에서 에러메시지를 출력하고 프로그램을 바로 종료시켜버리네.

 

뭔가, 치명적인 오류나 반드시 종료시켜야 할 경우, assert를 이용해서 조건에 맞지 않으면 바로 종료시키면 될 듯 하다.

'C' 카테고리의 다른 글

parameter 실수  (1) 2022.11.08
variable arg  (1) 2022.10.03
memset  (0) 2022.08.23
[Socket] thread  (0) 2022.06.28
function pointer 함수 포인터  (0) 2022.06.27

흔히 memset 을 사용하는 용도로는 함수명 그대로 memory에 저장된 값을 특정한 값으로 치환하기 위해서다.

 

예를 들면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
#include<string.h>
 
int main()
{
    char str[30= "hello, world";
 
    printf("%s\n", str);
 
    memset(str, 0sizeof(str));
 
    printf("%s\n", str);
    
 
    return 0;
}  
cs

위의 코드는 "hello, world" 라는 문자열로 초기화 되어 있는 char 배열 str을 널값 0 로 초기화하는 것이다.

 

당연히 출력하면 아무것도 안 나온다.

 

함수 원형은 다음과 같다.

 

void* memset(void* ptr, int value, size_t num);

 

먼저 초기화 할 데이터의 주솟값을 첫 인자로, 수정할 값을 두번째, 마지막으로 길이(바이트 단위) 이다.

 

보통은 0으로 초기화를 한다. 예를 들면 int 형 배열을 0으로 초기화 할 때, 반복문을 사용해도 되지만 memset을 이용할 수도 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
 
int main()
{
    char str[30= "hello, world";
    
    int arr[20];
 
    printf("%s\n", str);
 
    memset(str, 0sizeof(str));
 
    printf("%s\n", str);
 
    for(int i = 0; i < sizeof(arr) / sizeof(int); i++)
        arr[i] = 1;
 
    for(int i = 0; i < sizeof(arr) / sizeof(int); i++)
        printf("%d ", arr[i]);
    
   memset(arr, 0, 20 * sizeof(int));
 
    for(int i = 0; i < sizeof(arr) / sizeof(int); i++)
        printf("%d ", arr[i]);
 
    return 0;
}  
cs

 

위의 코드에서 1로 초기화 한 arr[20] 은 memset을 통해 0으로 치환된다. 문제는 memset 을 통해 값을 치환할 경우 0 이나 -1은 제 값이 잘 들어가는데, 이 외의 다른 값들은 이상하게 들어간다. 출력하면 이렇게 나온다.

 

memset(arr, 1, sizeof(int) * 8)

arr[20] 의 값을 byte 단위로 총 32byte 만큼 1로 초기화 했다. 상식 선에서는 1, 1, 1, ... 1 이렇게 출력될 것 같지만, 실제는 저 숫자, 84215045 이렇게 출력된다. 왜 그럴까?

 

디버깅에서 저장된 메모리를 확인하면 다음과 같다.

memset(arr, 1, sizeof(int)*20)

각 바이트는 1로 초기화 되었고, int는 4 바이트이므로 0x01010101 로 초기화되었다.

 

출력 화면은 다음과 같다. 16843009, 계산기로 확인결과 값은 마찬가지다.

출력 화면

아, memset은 바이트 단위로 초기화하기 때문에 int 형으로 출력하게 되면 4바이트를 읽어들여 10진수로 변환한다.

그래서 0x01010101 을 10진수로 변환해 16843009 란 값이 나오는구나...

 

그럼 왜?? -1은 잘 될까? 그 이유는 -1은 0xFFFFFFFF 의 10진수 정수이므로 문제없이 출력이 된 것이다.

 

memset의 활용은 아무래도 문자열의 문자를 변경할때가 아닌가 싶다. 1바이트 단위로 값을 변경하거나, 0으로 초기화 할때 반복문을 활용하는 것보단 memset으로 쉽게 초기화하는게 맞지 않나 싶다.

'C' 카테고리의 다른 글

variable arg  (1) 2022.10.03
assert  (0) 2022.08.27
[Socket] thread  (0) 2022.06.28
function pointer 함수 포인터  (0) 2022.06.27
[Socket] close()  (0) 2022.06.25

+ Recent posts