เหตุใด (เฉพาะ) คอมไพเลอร์บางตัวจึงใช้แอดเดรสเดียวกันสำหรับตัวอักษรสตริงที่เหมือนกัน


92

https://godbolt.org/z/cyBiWY

ฉันเห็น'some'ตัวอักษรสองตัวในโค้ดแอสเซมเบลอร์ที่สร้างโดย MSVC แต่มีเพียงตัวเดียวที่มี clang และ gcc สิ่งนี้นำไปสู่ผลลัพธ์ของการเรียกใช้โค้ดที่แตกต่างกันโดยสิ้นเชิง

static const char *A = "some";
static const char *B = "some";

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

ใครสามารถอธิบายความแตกต่างและความคล้ายคลึงกันระหว่างผลลัพธ์การคอมไพล์เหล่านั้นได้บ้าง เหตุใด clang / gcc จึงเพิ่มประสิทธิภาพบางอย่างแม้ว่าจะไม่มีการร้องขอการปรับให้เหมาะสมก็ตาม นี่เป็นพฤติกรรมที่ไม่ได้กำหนดหรือไม่?

ฉันยังสังเกตเห็นว่าถ้าฉันเปลี่ยนการประกาศเป็นที่แสดงด้านล่าง clang / gcc / msvc จะไม่ทิ้ง"some"รหัสแอสเซมเบลอร์เลย ทำไมพฤติกรรมถึงแตกต่างกัน?

static const char A[] = "some";
static const char B[] = "some";

4
stackoverflow.com/a/52424271/1133179คำตอบที่ดีสำหรับคำถามที่เกี่ยวข้องอย่างใกล้ชิดพร้อมด้วยเครื่องหมายคำพูดมาตรฐาน
luk32


6
สำหรับ MSVC ตัวเลือกคอมไพเลอร์ / GF จะควบคุมลักษณะการทำงานนี้ ดูdocs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd

1
FYI สิ่งนี้สามารถเกิดขึ้นได้สำหรับฟังก์ชันเช่นกัน
user541686

คำตอบ:


109

นี่ไม่ใช่พฤติกรรมที่ไม่ได้กำหนด แต่เป็นพฤติกรรมที่ไม่ระบุ สำหรับตัวอักษรของสตริง ,

อนุญาตให้คอมไพเลอร์รวมหน่วยเก็บข้อมูลสำหรับลิเทอรัลสตริงที่เท่ากันหรือทับซ้อนกันได้ แต่ไม่จำเป็น นั่นหมายความว่าตัวอักษรสตริงที่เหมือนกันอาจเปรียบเทียบกันหรือไม่ก็ได้เมื่อเปรียบเทียบโดยตัวชี้

นั่นหมายถึงผลของA == Bอาจจะเป็นtrueหรือfalseที่คุณไม่ควรขึ้นอยู่

จากมาตรฐาน[lex.string] / 16 :

ตัวอักษรสตริงทั้งหมดมีความแตกต่างกันหรือไม่ (นั่นคือถูกเก็บไว้ในอ็อบเจ็กต์ nonoverlapping) และการประเมินต่อเนื่องของสตริง - ลิเทอรัลจะให้อ็อบเจ็กต์เดียวกันหรือแตกต่างกันหรือไม่


36

คำตอบอื่น ๆ อธิบายว่าทำไมคุณไม่สามารถคาดหวังว่าที่อยู่ของตัวชี้จะแตกต่างกัน แต่คุณสามารถเขียนสิ่งนี้ใหม่ได้อย่างง่ายดายด้วยวิธีที่รับประกันAและBไม่เปรียบเทียบว่าเท่ากัน:

static const char A[] = "same";
static const char B[] = "same";// but different

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

ความแตกต่างที่เกิดขึ้นAและBตอนนี้คืออาร์เรย์ของอักขระ ซึ่งหมายความว่าพวกเขาไม่ใช่พอยน์เตอร์และที่อยู่ของพวกเขาจะต้องแตกต่างกันเช่นเดียวกับตัวแปรจำนวนเต็มสองตัวที่จะต้องเป็น C ++ ทำให้สิ่งนี้สับสนเพราะทำให้พอยน์เตอร์และอาร์เรย์ดูเหมือนจะใช้แทนกันได้ ( operator*และoperator[]ดูเหมือนจะทำงานเหมือนกัน) แต่มันต่างกันจริงๆ เช่นบางสิ่งบางอย่างconst char *A = "foo"; A++;ถูกกฎหมายอย่างสมบูรณ์ แต่const char A[] = "bar"; A++;ไม่ใช่

วิธีหนึ่งในการคิดถึงความแตกต่างคือchar A[] = "..."พูดว่า "ให้บล็อกความทรงจำและเติมด้วยตัวอักษร...ตามด้วย\0" ในขณะที่char *A= "..."บอกว่า "ให้ที่อยู่ที่ฉันสามารถค้นหาอักขระ...ตามด้วย\0"


8
นี่จะเป็นคำตอบที่ดียิ่งขึ้นหากคุณสามารถอธิบายได้ว่าทำไมจึงแตกต่างกัน
Mark Ransom

โปรดสังเกตว่า*pและp[0]ไม่เพียง แต่ "ดูเหมือนจะทำงานเหมือนกัน" แต่ตามนิยามแล้วจะเหมือนกัน (โดยมีเงื่อนไขว่าp+0 == pเป็นความสัมพันธ์เอกลักษณ์เนื่องจาก0เป็นองค์ประกอบที่เป็นกลางในการเพิ่มตัวชี้ - จำนวนเต็ม) หลังจากที่ทุกคนถูกกำหนดให้เป็นp[i] *(p+i)คำตอบนั้นเป็นประเด็นที่ดี
Peter - Reinstate Monica

typeof(*p)และtypeof(p[0])เป็นทั้งสองcharอย่างจึงเหลือไม่มากที่อาจแตกต่างกัน ฉันยอมรับว่า 'ดูเหมือนจะมีพฤติกรรมเหมือนกัน' ไม่ใช่ถ้อยคำที่ดีที่สุดเพราะความหมายแตกต่างกันมาก โพสต์ของคุณทำให้ผมนึกถึงวิธีที่ดีที่สุดในการเข้าถึงองค์ประกอบของภาษา C ++ อาร์เรย์: 0[p], 1[p], 2[p]ฯลฯ นี่คือวิธีข้อดีทำมันอย่างน้อยเมื่อพวกเขาต้องการที่จะสร้างความสับสนให้คนที่เกิดหลังจากการเขียนโปรแกรมภาษา C
tobi_s


นี่เป็นสิ่งที่น่าสนใจและฉันถูกล่อลวงให้เพิ่มลิงก์ไปยังคำถามที่พบบ่อยเกี่ยวกับ C แต่ฉันตระหนักว่ามีคำถามที่เกี่ยวข้องมากมาย แต่ดูเหมือนจะไม่มีใครตัดตรงประเด็นของคำถามนี้ได้ที่นี่
tobi_s

23

ไม่ว่าคอมไพเลอร์จะเลือกใช้ตำแหน่งสตริงเดียวกันหรือไม่AและBขึ้นอยู่กับการนำไปใช้งาน โดยปกติคุณสามารถพูดได้ว่าพฤติกรรมของรหัสของคุณไม่ได้ระบุไว้

ทั้งสองตัวเลือกใช้มาตรฐาน C ++ อย่างถูกต้อง


พฤติกรรมของรหัสคือทั้งโยนข้อยกเว้นหรือไม่ทำอะไรเลยเลือกก่อนที่จะเป็นครั้งแรกรหัสจะถูกดำเนินการในแฟชั่นที่ไม่ได้ระบุ นั่นไม่ได้หมายความว่าพฤติกรรมโดยรวมนั้นไม่ได้ระบุไว้ - เพียง แต่คอมไพเลอร์สามารถเลือกพฤติกรรมอย่างใดอย่างหนึ่งในลักษณะใดก็ได้ที่เห็นว่าเหมาะสมก่อนครั้งแรกที่สังเกตพฤติกรรม
supercat

3

เป็นการเพิ่มประสิทธิภาพเพื่อประหยัดพื้นที่ซึ่งมักเรียกว่า "string pooling" นี่คือเอกสารสำหรับ MSVC:

https://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

ดังนั้นหากคุณเพิ่ม / GF ในบรรทัดคำสั่งคุณจะเห็นพฤติกรรมเดียวกันกับ MSVC

อย่างไรก็ตามคุณไม่ควรเปรียบเทียบสตริงผ่านพอยน์เตอร์เช่นนั้นเครื่องมือวิเคราะห์แบบคงที่ที่เหมาะสมจะตั้งค่าสถานะรหัสนั้นว่ามีข้อบกพร่อง คุณต้องเปรียบเทียบสิ่งที่ชี้ไปไม่ใช่ค่าตัวชี้จริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.