문자열 리터럴로 char []를 초기화하는 것이 좋지 않습니까?


44

CodeGuru 에서 "strlen vs sizeof" 라는 제목의 스레드를 읽었 으며 그 중 하나는 "어쨌든 char문자열 리터럴이 있는 배열 을 초기화하는 것은 좋지 않습니다" 라고 말합니다 .

이것이 사실입니까, 아니면 단지 그의 ( "엘리트 회원") 의견입니까?


원래 질문은 다음과 같습니다.

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

권리. 크기는 길이에 1을 더한 값이어야합니까?

이것은 출력입니다

the size of september is 8 and the length is 9

크기는 반드시 10이어야합니다. strcpy에 의해 변경되기 전에 문자열의 크기를 계산하는 것과 비슷하지만 길이는 같습니다.

내 구문에 문제가 있습니까?


답장 은 다음과 같습니다 .

어쨌든 문자열 리터럴로 char 배열을 초기화하는 것은 나쁜 습관입니다. 따라서 항상 다음 중 하나를 수행하십시오.

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");

첫 번째 줄의 "const"에 유의하십시오. 저자가 c 대신 c ++를 가정했을 수 있습니까? 리터럴은 const 여야하고 최근 c ++ 컴파일러는 const 리터럴을 비 const 배열에 할당하는 것에 대한 경고 (또는 오류)를주기 때문에 C ++에서는 "나쁜 방법"입니다.
André

@ André C ++는 문자열 리터럴을 const 배열로 정의합니다. 왜냐하면 이것이 문자를 다루는 유일한 안전한 방법이기 때문입니다. C 문제 가되지 않으므로 안전한 것을 강제하는 사회적 규칙이 있습니다.
Caleth

@ 캘리 스. 나는 답글 작성자가 C ++ 관점에서 "나쁜 연습"에 접근하고 있다고 주장하려고 더 많이 알고 있었다.
André

@ André 연습 이 아니기 때문에 C ++에서는 나쁜 습관 이 아닙니다 . 그것은 해야한다 C에서 입력 오류, 그러나 당신이 당신이 말하는 스타일 가이드 규칙을 가지고 그래서 "그것은 금지 것"아니다
Caleth

답변:


59

어쨌든 문자열 리터럴로 char 배열을 초기화하는 것은 나쁜 습관입니다.

그 의견의 저자는 결코 그 의견을 정당화하지 않으며, 나는 수수께끼의 진술을 발견합니다.

C에서 (그리고 이것을 C로 태그했습니다), 그것은 문자열 값으로 배열 을 초기화 하는 유일한 방법입니다 char(초기화는 할당과 다릅니다). 둘 중 하나를 쓸 수 있습니다

char string[] = "october";

또는

char string[8] = "october";

또는

char string[MAX_MONTH_LENGTH] = "october";

첫 번째 경우, 배열의 크기는 이니셜 라이저의 크기에서 가져옵니다. 문자열 리터럴은 char0 바이트로 끝나는 배열로 저장되므로 배열의 크기는 8 ( 'o', 'c', 't', 'o', 'b', 'e', ​​'r', 0). 두 번째 두 경우, 배열의 크기는 선언의 일부로 지정됩니다 (8과 MAX_MONTH_LENGTH).

당신이 할 수없는 것은

char string[];
string = "october";

또는

char string[8];
string = "october";

첫 번째 경우, 배열 크기가 지정되지 않았고 크기를 가져올 초기화자가 없기 때문에 의 선언 string불완전 합니다. 두 경우 모두 a) 대입의 대상이 아닐 수 =있는 배열 식 string및 b) =어쨌든 한 배열의 내용을 다른 배열로 복사하도록 연산자가 정의되어 있지 않기 때문에 작동하지 않습니다.

같은 토큰으로, 당신은 쓸 수 없습니다

char string[] = foo;

foo또 다른 배열은 어디 입니까 char? 이 형식의 초기화는 문자열 리터럴에서만 작동합니다.

편집하다

배열 스타일의 이니셜 라이저를 사용하여 문자열을 보유하도록 배열을 초기화 할 수도 있다고 말하기 위해 이것을 수정해야합니다.

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

또는

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

그러나 문자열 리터럴을 사용하는 것이 더 쉽습니다.

편집 2

선언 외부에서 배열 의 내용 을 할당하려면 strcpy/strncpy(0으로 끝나는 문자열의 경우) 또는 memcpy(다른 유형의 배열의 경우) 을 사용해야합니다 .

if (sizeof string > strlen("october"))
  strcpy(string, "october");

또는

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!


@ KeithThompson : 동의하지 않는, 완전성을 위해 추가했습니다.
John Bode

16
그것은 char[8] str = "october";나쁜 습관입니다. 말 그대로 문자 확인이 오버 플로우 아니었다 만들기 위해 자신을 계산했다 그것을에서 맞춤법 오류를 수정 ... 예를 들어, 유지 보수에 따라 나누기 seprateseparate크기가 업데이트되지 않을 경우 중단됩니다.
djechlin

1
나는 djechlin에 동의하는데, 주어진 이유로 나쁜 습관입니다. JohnBode의 답변은 "나쁜 연습"측면에 대해 전혀 언급하지 않으며 (질문의 주요 부분입니다!) 배열을 초기화하기 위해 할 수 있거나 할 수없는 것을 설명합니다.
mastov 2016 년

마이너 : 일부터 '길이'값이 반환 strlen()사용하여 널 (null) 문자를 포함하지 않습니다 MAX_MONTH_LENGTH에 필요한 최대 크기를 유지하기 위해 char string[]종종 보인다 . 잘못 IMO, MAX_MONTH_SIZE더 나은 여기에있을 것입니다.
chux - 분석 재개 모니카

10

내가 기억하는 유일한 문제는 문자열 리터럴을 char *다음에 할당하는 것입니다 .

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

예를 들어이 프로그램을 보자 :

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

읽기 전용으로 표시된 페이지에 쓰려고하면 내 플랫폼 (Linux)에서 충돌이 발생합니다. 다른 플랫폼에서는 '9 월'등을 인쇄 할 수 있습니다.

말 그대로-리터럴로 초기화하면 특정 양의 예약이 이루어 지므로 작동하지 않습니다.

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

그러나 이것은

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

마지막으로-전혀 사용하지 않을 것입니다 strcpy:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

일부 컴파일러는 안전한 호출로 변경할 수 있지만 strncpy훨씬 안전합니다.

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';

strncpylength something_else가보다 큰 경우 복사 된 문자열을 null로 종료하지 않기 때문에 여전히 버퍼 오버런의 위험 이 sizeof(buf)있습니다. 나는 보통 그것을 buf[sizeof(buf)-1] = 0막기 위해 마지막 문자 를 설정 하거나 buf0으로 초기화되면 sizeof(buf) - 1복사 길이로 사용합니다.
syockit 2016 년

사용 strlcpy하거나 strcpy_s또는 snprintf당신이있는 경우.
user253751

결정된. 당신이 최신 컴파일러 작업의 고급 스러움이 (하지 않는 한 불행히도이 일을 쉬운 휴대용 방법은 없습니다 strlcpysnprintfMSVC에 직접 액세스 할 수없는, 이상 주문시와 strcpy_s* nix에서 스크립트에없는).
Maciej Piechotka

@MaciejPiechotka : 유닉스가 마이크로 소프트가 후원 한 부속서 k를 거부 한 것에 대해 감사드립니다.
중복 제거기

6

스레드가 발생하지 않는 한 가지는 다음과 같습니다.

char whopping_great[8192] = "foo";

vs.

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

전자는 다음과 같은 작업을 수행합니다.

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

후자는 memcpy 만 수행합니다. C 표준은 배열의 일부가 초기화되면 모든 것이 초기화되어야한다고 주장합니다. 따라서이 경우 직접하는 것이 좋습니다. 저것은 tress가 받고 있었던 것일지도 모른다고 생각합니다.

확실히

char whopping_big[8192];
whopping_big[0] = 0;

어느 것보다 낫다 :

char whopping_big[8192] = {0};

또는

char whopping_big[8192] = "";

ps 보너스 포인트의 경우 다음을 수행 할 수 있습니다.

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

배열을 오버플로하려고하면 컴파일 시간을 0으로 나누십시오.


5

주로 char[]프로그램 내에서 쉽게 사용할 수있는 변수 / 구문 의 크기가 없기 때문에 .

링크의 코드 샘플 :

 char string[] = "october";
 strcpy(string, "september");

string스택에 7 ~ 8 자 길이로 할당됩니다. 이 방법으로 null로 종료되었는지 여부를 기억할 수 없습니다. 연결 된 스레드가 해당 스레드라고 명시했습니다.

해당 문자열 위에 "9 월"을 복사하는 것은 명백한 메모리 오버런입니다.

string다른 함수에 전달 하면 다른 함수가 배열에 쓸 수있는 또 다른 문제가 발생합니다 . 당신은 그래서 배열이 얼마나 다른 기능을 말할 필요 오버런을 만들지 않습니다. string결과와 함께 전달할 수 strlen()있지만 스레드 string가 null로 끝나지 않으면 스레드가 어떻게 폭발 할 수 있는지 설명합니다 .

고정 된 크기 (바람직하게는 상수로 정의 됨)로 문자열을 할당 한 다음 배열과 고정 된 크기를 다른 함수에 전달하는 것이 좋습니다. @John Bode의 의견은 정확하며 이러한 위험을 완화 할 수있는 방법이 있습니다. 또한 사용하기 위해 더 많은 노력이 필요합니다.

내 경험상, char[]to를 초기화 한 값 은 일반적으로 거기에 배치 해야하는 다른 값에 비해 너무 작습니다. 정의 된 상수를 사용하면 해당 문제를 피할 수 있습니다.


sizeof string버퍼 크기 (8 바이트)를 제공합니다. strlen기억이 걱정되는 대신 그 표현의 결과를 사용하십시오 .
마찬가지로 호출하기 전에 strcpy대상 버퍼가 소스 문자열에 충분한 지 확인하기 위해 확인할 수 있습니다 if (sizeof target > strlen(src)) { strcpy (target, src); }.
예, 배열을 함수에 전달해야하는 경우 물리적 크기도 전달해야합니다 foo (array, sizeof array / sizeof *array);. – 존 보데


2
sizeof string버퍼 크기 (8 바이트)를 제공합니다. strlen기억이 걱정되는 대신 그 표현의 결과를 사용하십시오 . 마찬가지로 호출하기 전에 strcpy대상 버퍼가 소스 문자열에 충분한 지 확인하기 위해 확인할 수 있습니다 if (sizeof target > strlen(src)) { strcpy (target, src); }. 예, 배열을 함수에 전달해야하는 경우 물리적 크기도 전달해야합니다 foo (array, sizeof array / sizeof *array);.
John Bode

1
@ JohnBode-감사합니다. 좋은 지적입니다. 귀하의 의견을 답변에 포함 시켰습니다.

1
더 정확하게 말하면 배열 이름에 대한 대부분의 참조는 배열 의 첫 번째 요소를 가리키는로 string암시 적으로 변환됩니다 char*. 배열 범위 정보가 손실됩니다. 함수 호출은 이러한 상황이 발생하는 많은 상황 중 하나 일뿐입니다. char *ptr = string;또 다른 것입니다. string[0]이것의 예 조차도 ; []운영자는 직접적으로 배열에, 포인터에서 작동합니다. 추천 자료 : comp.lang.c FAQ의 6 절 .
Keith Thompson

마지막으로 실제로 질문에 대한 답변입니다!
mastov

2

"나쁜 실천"아이디어는 다음과 같은 사실에서 비롯된 것입니다.

char string[] = "october is a nice month";

소스 머신 코드에서 스택으로 암묵적으로 strcpy를 만듭니다.

해당 문자열에 대한 링크 만 처리하는 것이 더 효율적입니다. 같이 :

char *string = "october is a nice month";

또는 직접 :

strcpy(output, "october is a nice month");

(물론 대부분의 코드에서는 중요하지 않을 것입니다)


수정하려고하면 사본 만 만들어지지 않습니까? 나는 컴파일러가 그것보다 더 똑똑 할 것이라고 생각할 것이다
Cole Johnson

1
무엇 같은 경우에 대해 char time_buf[] = "00:00";당신이 버퍼를 수정 될거야 어디? char *문자열 리터럴로 초기화가 너무 문자열 리터럴의 저장 방법은 알 수없는 (구현 정의)이기 때문에이 정의되지 않은 동작의 결과를 수정하려고, 첫 번째 바이트의 주소로 설정되어, a의 바이트를 수정하는 동안은 char[]완벽하게 합법적 때문이다 초기화는 바이트를 스택에 할당 된 쓰기 가능한 공간에 복사합니다. 뉘앙스를 자세히 설명하지 않고 "비효율적"또는 "나쁜 습관"이라고 말하는 char* vs char[]것은 오도의 소지가 있습니다.
Braden Best

-3

결코 오래 걸리지는 않지만 "string"은 const char *이고 char *에 할당하기 때문에 char []를 문자열로 초기화하지 않아야합니다. 따라서이 char []를 데이터를 변경하는 메소드에 전달하면 흥미로운 동작을 가질 수 있습니다.

칭찬에 따르면, 나는 char []와 char *를 섞었다.

char 배열에 데이터를 할당하는 데 아무런 문제가 없지만이 배열을 사용하기 위해 'string'(char *)으로 사용하는 것이기 때문에이 배열을 수정해서는 안된다는 것을 잊기 쉽습니다.


3
잘못되었습니다. 초기화는 문자열 리터럴의 내용을 배열로 복사합니다. const그런 식으로 정의 하지 않는 한 배열 객체는 아닙니다 . (그리고 C에서 문자열 리터럴 없습니다 const. 문자열 리터럴을 수정하려는 시도가 정의되지 않은 동작을 가지고 있지만은) char *s = "literal";당신이에 대해 얘기하고 행동의 종류를 가지고있다; 그것은 더 잘 쓰여const char *s = "literal";
키이스 톰슨

실제로 내 잘못은 char []를 char *와 혼합 한 것입니다. 그러나 내용을 배열에 복사하는 것에 대해서는 확신하지 못합니다. MS C 컴파일러를 사용한 빠른 검사에서 'char c [] = "asdf";' const 세그먼트에 'string'을 만든 다음이 주소를 배열 변수에 할당합니다. 그것이 실제로 const가 아닌 char 배열에 대한 할당을 피하는 것에 대해 말한 이유입니다.
Dainius

나는 회의적입니다. 이 프로그램을 사용 해보고 어떤 결과를 얻었는지 알려주십시오.
Keith Thompson

2
"일반적으로"asdf "는 상수이므로 const로 선언해야합니다." - 상수 이기 때문에 동일한 추론이 conston을 호출합니다 . int n = 42;42
Keith Thompson

1
어떤 기계를 사용하든 상관 없습니다. 언어 표준 c에 따라 수정 가능합니다. 1 + 1평가하는 것만 큼 ​​강력합니다 2. 경우 내가 링크 된 프로그램이 위의 인쇄 이외의 아무것도하지 EFGH, 그것은 부적합 C 구현을 나타냅니다.
Keith Thompson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.