qsort() 문자열 정렬 관련 질문

야구볼까축구볼까@Naver의 이미지

입력받은 문자열을 qsort함수를 이용해 정렬하려고 하는데 제대로 정렬되지 않습니다. 무엇이 문제일까요?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define MAX_SIZE 10
 
int compare_string(const void *first, const void *second)
{
	return (strcmp((char*)first, (char*)second));
}
 
int input_data(char **arr)
{
	char string[200];
	char *token;
	int arr_size;
 
	gets(string);
 
	token = strtok(string, " ");
	arr[0] = _strdup(token);
	arr_size = 1;
	do
	{
		token = strtok(NULL, " ");
		if (token == NULL)
			break;
		arr[arr_size++] = _strdup(token);
	} while (1);
 
	return arr_size;
}
 
int main()
{
	char **arr;
	int arr_size;
	int i;
 
	arr = (char**)malloc(sizeof(char*) * MAX_SIZE);
	arr_size = input_data(arr);
 
	qsort((void*)arr, arr_size, sizeof(arr[0]), compare_string);
 
	for (i = 0; i < arr_size; i++)
		printf("%s ", arr[i]);
 
	free(arr);
 
	return 0;
}

입력 : zoo apple car banana melon
원하는 출력 : apple banana car melon zoo

 의 이미지

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define MAX_SIZE 10
 
int compare_string(const void *first, const void *second)
{
	// return (strcmp((char*)first, (char*)second));
	// qsort의 비교 함수에는 배열의 원소가 아니라
	// 배열 원소를 가리키는 포인터가 들어옵니다.
	// 이 경우는 어느 쪽이든 포인터이기 때문에 헷갈리신 것 같네요.
	// 이래서 이중 포인터를 쓸 때는 정말 조심해야 합니다.
	return (strcmp(*(char**)first, *(char**)second));
}
 
int input_data(char **arr)
{
	char string[200];
	char *token;
	int arr_size;
 
	gets(string);
 
	token = strtok(string, " ");
	arr[0] = _strdup(token);
	arr_size = 1;
	do
	{
		token = strtok(NULL, " ");
		if (token == NULL)
			break;
		arr[arr_size++] = _strdup(token);
	} while (1);
 
	return arr_size;
}
 
int main()
{
	char **arr;
	int arr_size;
	int i;
 
	arr = (char**)malloc(sizeof(char*) * MAX_SIZE);
	arr_size = input_data(arr);
 
	qsort((void*)arr, arr_size, sizeof(arr[0]), compare_string);
 
	for (i = 0; i < arr_size; i++)
		printf("%s ", arr[i]);
 
	free(arr);
 
	return 0;
}
야구볼까축구볼까@Naver의 이미지

친절하고 설명 감사합니다!!!

함수의 동작원리도 제대로 이해안하고 그냥 가져다 쓰다 보니 이런 문제가 나오네요;;

그럼 한 가지 더 궁금한 점이 있습니다!

배열을 위와 같이 동적 할당하지 않고 2차배열로 선언해서

char array[5][10] = {"zoo", "apple", "car", "banana", "melon"};

비교 함수를 질문했던 방식으로
int compare_string(const void *first, const void *second)
{
    return (strcmp((char*)first, (char*)second));
}

하면 정상적으로 정렬되는 이유는 무엇인가요?

비교함수에 들어가는 원소, 즉 first, second에 들어가는 값이

동적할당한 배열과 직접 선언한 2차원 배열이 달라서 그런건가요?

 의 이미지

C언어에서 배열과 포인터의 관계는 오묘하기 그지없습니다.
1차원 배열을 쓸 때는 그 차이를 의식할 필요가 거의 없으며 어지간하면 프로그래머가 바라는 대로 동작합니다.
2차원 배열부터는 얘기가 달라집니다.

char **arrchar array[5][10]을 비교해 보면, 우선 전체적인 메모리 구조부터 다르지요.

전자는 우선 arrchar *의 배열을 가리키고, 그 배열의 각 원소가 char의 배열을 가리키는 구조입니다. 실질적으로 char 타입의 원소를 접근하기 위해서는 두 번의 포인터 역참조가 필요한 구조이지요.

후자는 5 × 10바이트의 연속된 메모리 공간입니다. 이 구조는 char * 타입의 어떤 포인터도 저장하고 있지 않습니다.

여기까지 이해가 잘 안 되신다면, 종이를 한 장 가져오셔서 두 경우에서 메모리가 어떻게 할당되어 어떻게 가리키고 있는지 그려 보세요.

qsort는 매개변수가 어떤 포인터인지 알 수 없으므로(void *로 캐스팅 되어 들어오니까요.) 그 포인터에 원소의 크기(셋째 매개변수)의 배수를 더하여 각 원소의 주소를 얻습니다. arr의 경우 원소의 크기는 sizeof(char *)일 것이고, array의 경우는 10이겠죠.

arr의 값에 sizeof(char *)를 한 번 더해 봤자 arr가 가리키는 char * 배열의 둘째 원소의 주소일 뿐이죠. 굳이 타입으로 나타내자면 char **입니다. 한 번 역참조해야 strcmp에 넘겨줄 문자열 주소가 나오는 겁니다.

반면에 array의 값에 10을 더해주면 앞서 말씀드린 5 × 10바이트의 연속된 메모리 공간의 앞에서 10바이트 지점의 주소가 됩니다. 바로 "apple"이 있는 주소이지요. 사실 그 타입은 엄밀히 따지면 char (*)[10]라고 할 만 합니다만, 그 값은 strcmp에 넘겨줄 수 있는 문자열 포인터 그대로이기 때문에 char *로 강제 변환하여 고스란히 쓸 수 있습니다.

설명하기도 복잡하네요. 원래 &arr[1]&array[1]의 예를 들어 가면서 C언어의 배열-포인터 변환에 대해서 본격적으로 설명드려볼까 했습니다만, 안 그래도 복잡한 설명이 더 복잡해질 것 같아서 관뒀습니다. 어차피 여기 쟁점은 qsort의 동작이니까요.

제가 설명을 못 한 탓도 없지 않겠습니다만 원래 C언어에서 배열이라는 게 생각 없이 쓰기는 딱 좋은데 파고들어야 할 일이 생기면(특히 2차원 이상) 골치아파지는 측면이 있습니다. 사실 프로그래밍 하면서 이런 위험한 경우는 정말 자신있지 않으면 피해서 가는 편이 좋습니다. 자신 있다고 해도 최대한 주의해서 쓰는 편이 바람직하고요.

야구볼까축구볼까@Naver의 이미지

정말 명쾌합니다!!!

처음 배울 때 원리를 제대로 이해하지 않고 사용법만 보고 그대로 따라 사용하니 이런 문제가 발생한 것같습니다.

답변자님의 답변을 보고 완전히 이해하지 않고 그냥 방법을 가져다 쓰는 제 잘못된 프로그래밍 습관을 깨닫게 되었습

니다. 그리고 복잡할 수 도 있는 내용을 알기 쉽게 설명하시는 답변자님의 내공이 정말 대단하시네요. 덕분에 문

제도 해결하고 제 습관에 대해 다시 한 번 생각해 볼 수 있었던 좋은 계기가 되었습니다. 진심으로 감사합니다.

초보임의 이미지

질문자는 아니지만 굉장히 헤매던 중 정말 큰 도움이 되었습니다!!!
현자님..

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.