조건부 연산자를 사용할 때 C에서 문자열 연결을 허용하지 않는 이유는 무엇입니까?


95

다음 코드는 문제없이 컴파일됩니다.

int main() {
    printf("Hi" "Bye");
}

그러나 이것은 컴파일되지 않습니다.

int main() {
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));
}

그 이유는 무엇입니까?


95
문자열 연결은 초기 렉싱 단계의 일부입니다. 이는 C의 synatx 표현식의 일부가 아닙니다. 즉, "문자열 리터럴"유형의 이 없습니다 . 오히려 문자열 리터럴은 값을 형성하는 소스 코드의 어휘 요소입니다.
Kerrek SB

24
@KerrekSB 대답을 명확히하기 위해-문자열 연결은 코드 텍스트를 컴파일 하기 전에 코드 텍스트를 전처리하는 부분입니다 . 삼항 연산자는 런타임에서 평가되지만 코드가 컴파일 된 후 (또는 모든 것이 상수 인 경우 컴파일 타임에 수행 될 수 있습니다).
유진 Sh.

2
세부 사항 :이 게시물에 "Hi""Bye"있는 문자열 리터럴 이 아닌 문자열 은 C 표준 라이브러리에서 사용 된 바와 같이. 로 문자열 리터럴 , 컴파일러는 연결할 것입니다 "H\0i" "B\0ye". sprintf(buf,"%s%s", "H\0i" "B\0ye");
똑같지

15
당신이 할 수없는 동일한 이유a (some_condition ? + : - ) b
user253751

4
도 있습니다 printf("Hi" ("Bye"));, 그것은 삼항 연산자를 필요로하지 않습니다 - 작동하지 않습니다 괄호만으로도 충분합니다 ( printf("Hi" test ? "Bye" : "Goodbye")컴파일되지는 않지만). 문자열 리터럴 뒤에 올 수있는 토큰의 수는 제한되어 있습니다. 쉼표 ,, 여는 대괄호 [, 닫는 대괄호 ]( 1["abc"]— 및 예, 끔찍함), 닫는 대괄호 ), 닫는 중괄호 }(이니셜 라이저 또는 유사한 컨텍스트에서) 및 세미콜론 ;은 합법적입니다 (및 다른 문자열 리터럴). 다른 사람이 있는지 잘 모르겠습니다.
Jonathan Leffler

답변:


121

C 표준 (5.1.1.2 번역 단계)에 따름

1 번역의 구문 규칙 중 우선 순위는 다음 단계로 지정됩니다 .6)

  1. 인접한 문자열 리터럴 토큰이 연결됩니다.

그리고 그 후에야

  1. 토큰을 구분하는 공백 문자는 더 이상 중요하지 않습니다. 각 전처리 토큰은 토큰으로 변환됩니다. 결과 토큰은 구문 및 의미 론적으로 분석되고 번역 단위로 번역됩니다 .

이 건설에서

"Hi" (test ? "Bye" : "Goodbye")

인접한 문자열 리터럴 토큰이 없습니다. 따라서이 구성은 유효하지 않습니다.


43
이것은 C에서 허용되지 않는다는 주장을 반복 할뿐입니다 . 질문 인 이유를 설명하지 않습니다 . 왜 5 시간 동안 26 개의 업보를 축적했는지 모르겠습니다. 축하합니다.
궤도의 가벼운 경주

4
여기에서 @LightnessRacesinOrbit에 동의해야합니다. 왜 안 (test ? "Bye" : "Goodbye")문자열 리터럴 중 하나를 평가 해 본질적으로 만들기 "Hi" "Bye""Hi Goodbye"? (내 질문은 다른 답변에서 답변 됨)
Insane

48
@LightnessRacesinOrbit, 사람들이 일반적으로 C로 컴파일되지 않는 이유를 물을 때, 그들은 그것이 어떤 규칙을 위반하는지에 대한 설명을 요구하고 있으며, 왜 Standards Authors of Antiquity 가 그것을 그렇게 선택 했는지 가 아닙니다.
user1717828

4
@LightnessRacesinOrbit 설명하는 질문은 주제에서 벗어난 것일 수 있습니다. 나는 어떤 볼 수없는 기술적 인 모든 대답은 의견을 기반으로 할 것, 그래서 사양의 저자에서 확실한 대답하지 않고, 그 구현이 가능하지 왜 이유를. 그리고 일반적으로 "실용적"또는 "대답 가능한"질문의 범주에 속하지 않습니다 ( 도움말 센터에서 요구하는대로).
jpmc26

12
@LightnessRacesinOrbit "C 표준이 그렇게 말했기 때문에" 이유를 설명합니다 . 이 규칙이 정의 된대로 정의 된 이유에 대한 질문은 주제를 벗어납니다.
user11153

135

C11 표준, §5.1.1.2 장에 따라 인접한 문자열 리터럴 연결 :

인접한 문자열 리터럴 토큰이 연결됩니다.

번역 단계 에서 발생합니다 . 반면에 :

printf("Hi" (test ? "Bye" : "Goodbye"));

런타임에 평가되는 조건부 연산자가 포함됩니다 . 따라서 컴파일 타임에 번역 단계 중에 인접한 문자열 리터럴이 없으므로 연결이 불가능합니다. 구문이 잘못되어 컴파일러에서보고합니다.


이유 부분 에 대해 좀 더 자세히 설명하기 위해 전처리 단계 동안 인접한 문자열 리터럴이 연결되어 단일 문자열 리터럴 (토큰)로 표시됩니다. 그에 따라 스토리지가 할당되고 연결된 문자열 리터럴은 단일 엔티티 (하나의 문자열 리터럴) 로 간주됩니다 .

반면에 런타임 연결의 경우 대상에는 연결된 문자열 리터럴 을 보유하기에 충분한 메모리가 있어야합니다. 그렇지 않으면 예상되는 연결된 출력에 액세스 할 수있는 방법이 없습니다 . 지금의 경우 문자열 리터럴 , 그들은 이미 컴파일 시간에 메모리를 할당 할 수 없습니다 연장 더 이상 들어오는 입력에 맞게 으로 또는 추가 원본 콘텐츠에 . 즉, 연결된 결과를 단일 문자열 리터럴 로 액세스 (표시) 할 수있는 방법이 없습니다 . 따라서이 구조는 본질적으로 잘못되었습니다.

참고로 런타임 문자열 ( 리터럴 아님) 연결의 경우 strcat()문자열 을 연결 하는 라이브러리 함수 가 있습니다 . 설명은 다음을 언급합니다.

char *strcat(char * restrict s1,const char * restrict s2);

strcat()함수는에서 가리키는 문자열 s2의 끝에 (종료 널 문자 포함)가 가리키는 문자열의 사본을에서 가리키는 문자열 의 끝에 추가 합니다s1 . 의 초기 문자 s2는 끝에있는 null 문자를 덮어 씁니다 s1. [...]

그래서 우리는 이 문자열 리터럴s1 이 아니라 문자열 임을 알 수 있습니다 . 그러나의 내용은 어떤 식 으로든 변경되지 않으므로 문자열 리터럴 일 수 있습니다.s2


1
다음에 대한 추가 설명을 추가하고 싶을 수 있습니다 strcat. 대상 배열은 문자를받을 수있을만큼 길어야하며 s2이미 존재하는 문자 뒤에 null 종결자가 있어야합니다.
chqrlie

39

문자열 리터럴 연결은 컴파일 타임에 전처리기에 의해 수행됩니다. 이 연결이 다음의 값을 인식 할 방법이 없습니다.test 은 프로그램이 실제로 실행될 때까지 알려지지 않은 이 없습니다. 따라서 이러한 문자열 리터럴은 연결할 수 없습니다.

일반적인 경우는 컴파일 타임에 알려진 값에 대해 이와 같은 구성이 없기 때문에 C 표준은 자동 연결 기능을 가장 기본적인 경우로 제한하도록 설계되었습니다. .

그러나이 제한을 그런 식으로 표현하지 않았거나 제한이 다르게 구성되었다하더라도 연결을 런타임 프로세스로 만들지 않고서는 예제를 실현할 수 없습니다. 그리고이를 위해 strcat.


3
나는 단지 가정을 읽었습니다. 당신이 말하는 것은 거의 유효하지만 소스가 없기 때문에 소스를 제공 할 수 없습니다. C와 관련된 유일한 출처는 표준 문서입니다. (많은 경우에 모호하지만) 어떤 것들이 왜 그런지 설명하지 않고 특정 방식 이어야만한다고 말합니다. 그래서 모스크바의 대답에서 블라드에 대해 그토록 까다로운 행동은 부적절합니다. OP는 "왜 그런 식입니까?"로 나눌 수 있기 때문에 -유일한 정답은 "C이기 때문에 C가 정의 된 방식이기 때문에"라는 유일한 정답입니다.
dhein

1
이것은 설명이 부족합니다. 그러나 여기서 다시 beeing은 Vlad의 대답이 핵심 문제에 대한 설명으로 훨씬 더 많은 것을 제공한다고 말했습니다. 다시 한 번 말 : 내가 확인할 수있는 정보는 관련이 있고 정확하지만 귀하의 불만에 동의하지 않습니다. 그리고 나는 당신이 주제를 벗어났다고 생각하지는 않지만, 내 POV에서 더 주제를 벗어난 것입니다.
dhein

11
@Zaibis : 출처는 나입니다. Vlad의 대답은 전혀 설명이 아닙니다. 그것은 단지 질문의 전제에 대한 확인 일뿐입니다. 확실히 그들 중 어느 것도 "주제에서 벗어난 것"이 아닙니다 (이 용어가 의미하는 바를 찾아 볼 수 있습니다). 그러나 당신은 당신의 의견에 대한 권리가 있습니다.
궤도의 가벼운 경주

위의 의견을 읽은 후에도 OP 가이 답변에 대한 추가 설명을 요청하지 않는 한이 답변이 완벽한 답변이라고 믿습니다.이 답변을 누가 반대했는지 여전히 궁금합니다. ᶘ ᵒᴥᵒᶅ
Mohit Jain

2
나는 왜이 대답이 당신에게 받아 들여지고 @VladfromMoscow는 그렇지 않은지 구별 할 수 없습니다. 둘 다 같은 말을하고 그의 인용이 인용으로 뒷받침되고 당신은 그렇지 않습니다.
Marquis of Lorne

30

C에는 string유형 이 없기 때문 입니다. 문자열 리터럴은 포인터로 char참조되는 배열 로 컴파일됩니다 char*.

C는 첫 번째 예제에서와 같이 인접 리터럴컴파일 타임에 결합 할 수 있도록 합니다. C 컴파일러 자체에는 문자열에 대한 지식이 있습니다. 그러나이 정보는 런타임에 존재하지 않으므로 연결이 발생할 수 없습니다.

컴파일 과정에서 첫 번째 예제는 다음과 같이 "번역"됩니다.

int main() {
    static const char char_ptr_1[] = {'H', 'i', 'B', 'y', 'e', '\0'};
    printf(char_ptr_1);
}

프로그램이 실행되기 전에 컴파일러에서 두 문자열을 단일 정적 배열로 결합하는 방법에 유의하십시오.

그러나 두 번째 예는 다음과 같이 "번역"됩니다.

int main() {
    static const char char_ptr_1[] = {'H', 'i', '\0'};
    static const char char_ptr_2[] = {'B', 'y', 'e', '\0'};
    static const char char_ptr_3[] = {'G', 'o', 'o', 'd', 'b', 'y', 'e', '\0'};
    int test = 0;
    printf(char_ptr_1 (test ? char_ptr_2 : char_ptr_3));
}

이것이 컴파일되지 않는 이유가 명확해야합니다. 삼항 연산자 ?는 "문자열"이 더 이상 존재하지 않고 포인터에 char의해 참조되는 단순한 배열 로만 존재하는 컴파일 타임이 아닌 런타임에 평가됩니다 char*. 인접한 달리 문자열 리터럴 , 인접한 문자 포인터는 단순히 구문 오류가 있습니다.


2
훌륭한 대답, 아마도 여기에서 가장 좋습니다. "왜 이것이 컴파일되지 않는지 명확해야합니다." "삼항 연산자는 컴파일 시간이 아닌 런타임에 평가 된 조건이기 때문에"로 확장하는 것을 고려할 수 있습니다 .
cat

나머지 포인터에 대해서도 비슷 하지 static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};않아야 static const char *char_ptr_1 = "HiBye";합니까?
Spikatrix

@CoolGuy static const char *char_ptr_1 = "HiBye";컴파일러 를 작성할 때 라인을로 변환 static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};하므로 "문자열처럼"작성해서는 안됩니다. 답변에서 말했듯이 문자열은 문자 배열로 컴파일되며 가장 "원시"형식의 문자 배열을 할당하는 경우 쉼표로 구분 된 문자 목록을 사용합니다. 다음과 같이됩니다.static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush

3
@Ankush 예. 그러나 비록 static const char str[] = {'t', 'e', 's', 't', '\0'};과 동일 static const char str[] = "test";, static const char* ptr = "test";있다 없다 와 같은 static const char* ptr = {'t', 'e', 's', 't', '\0'};. 전자는 유효하고 컴파일되지만 후자는 유효하지 않으며 예상대로 수행합니다.
Spikatrix

마지막 단락을 구체화하고 코드 예제를 수정했습니다. 감사합니다!
Unsigned

13

두 분기 모두 런타임에 선택할 컴파일 시간 문자열 상수를 생성하도록하려면 매크로가 필요합니다.

#include <stdio.h>
#define ccat(s, t, a, b) ((t)?(s a):(s b))

int
main ( int argc, char **argv){
  printf("%s\n", ccat("hello ", argc > 2 , "y'all", "you"));
  return 0;
}

10

그 이유는 무엇입니까?

삼항 연산자를 사용하는 코드는 조건부로 두 문자열 리터럴 중에서 선택합니다. 알려 지거나 알려지지 않은 조건에 관계없이 컴파일 타임에 평가할 수 없으므로 컴파일 할 수 없습니다. 이 문장조차 printf("Hi" (1 ? "Bye" : "Goodbye"));컴파일되지 않습니다. 그 이유는 위의 답변에 자세히 설명되어 있습니다. 의 또 다른 가능성 컴파일 할 삼항 연산자가 유효 사용하여 이러한 문을은 , 또한 포함 할 형식의 태그 와 같은 포맷 된 삼항 연산자 문의 결과 추가 인수 로를 printf. 그럼에도 불구하고, printf()출력물은 이러한 문자열을 런타임시 에만 "연결된"느낌을줍니다 .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}

3
그래서 튜토리얼 사이트가 아닙니다. 튜토리얼이 아닌 OP에 대한 답변을 제공해야합니다.
Michi

1
이것은 OP의 질문에 대답하지 않습니다. OP의 근본적인 문제를 해결하려는 시도 일 수 있지만 이것이 무엇인지 실제로는 모릅니다.
Keith Thompson

1
printf형식 지정자가 필요 하지 않습니다 . 컴파일 타임에 연결 만 수행 된 경우 (그렇지 않은 경우) OP의 printf 사용이 유효합니다.
David Conrad

귀하의 의견에 감사드립니다, @David Conrad. 내 엉성한 문구는 마치 진술 printf()에 형식 태그가 필요한 것처럼 보이게 할 것입니다. 이것은 절대적으로 사실이 아닙니다. 수정되었습니다!
user3078414

더 나은 표현입니다. +1 감사합니다.
David Conrad

7

printf("Hi" "Bye"); 당신은 컴파일러가 하나의 배열로 만들 수있는 문자의 두 개의 연속적인 배열을 가지고있다.

에서는 printf("Hi" (test ? "Bye" : "Goodbye"));당신이 가진 하나 개의 배열 (배열의 첫 번째 요소에 대한 포인터로 변환) 문자 포인터 하였다. 컴파일러 는 배열과 포인터를 병합 할 수 없습니다 .


0

질문에 답하기 위해 printf의 정의로 이동합니다. printf 함수는 const char * 를 인수로 예상 합니다. "Hi"와 같은 문자열 리터럴은 const char *입니다. 그러나 다음 (test)? "str1" : "str2"과 같은 표현식은 const char *가 아닙니다. 왜냐하면 그러한 표현식의 결과는 런타임에서만 발견되고 따라서 컴파일 시간에 결정되지 않기 때문에 컴파일러가 불평하게 만드는 사실입니다. 반면에 이것은 완벽하게 잘 작동합니다.printf("hi %s", test? "yes":"no")


* 그러나 같은 표현 (test)? "str1" : "str2"은 NOT a const char*... 물론 그렇습니다! 상수 표현식은 아니지만 유형 const char * 입니다. 작성하는 것이 완벽 할 것 printf(test ? "hi " "yes" : "hi " "no")입니다. 영업 이익의 문제와는 아무 상관이없는 printf, "Hi" (test ? "Bye" : "Goodbye")아무리 표현의 맥락이 무엇인지 구문 오류입니다를.
chqrlie

동의합니다. 식의 출력을 식 자체와 혼동했습니다
Stats_Lover

-4

printf 함수의 매개 변수 목록이 다음과 같으므로 컴파일되지 않습니다.

(const char *format, ...)

("Hi" (test ? "Bye" : "Goodbye"))

매개 변수 목록에 맞지 않습니다.

gcc는 다음과 같이 상상하여 이해하려고합니다.

(test ? "Bye" : "Goodbye")

매개 변수 목록이고 "Hi"가 함수가 아니라고 불평합니다.


6
Stack Overflow에 오신 것을 환영합니다. printf()인수 목록 과 일치하지 않는다는 것이 맞습니다 . 그러나 표현식이 printf()인수 목록 에서만 유효하지 않기 때문 입니다. 즉, 문제에 대해 너무 전문적인 이유를 선택했습니다. 일반적인 문제는 "Hi" (C에서 유효하지 않다는 것입니다 printf(). 이 답변을 삭제하기 전에이 답변을 삭제하는 것이 좋습니다.
Jonathan Leffler

그것은 C가 작동하는 방식이 아닙니다. 이것은 PHP와 같은 문자열 리터럴을 호출하려는 것으로 구문 분석되지 않습니다.
cat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.