การเพิ่มประสิทธิภาพที่ไม่คาดคิดของ strlen เมื่อสร้างสมนามอาร์เรย์ 2 มิติ


28

นี่คือรหัสของฉัน:

#include <string.h>
#include <stdio.h>

typedef char BUF[8];

typedef struct
{
    BUF b[23];
} S;

S s;

int main()
{
    int n;

    memcpy(&s, "1234567812345678", 17);

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

    n = strlen((char *)&s) / sizeof(BUF);
    printf("%d\n", n);
}

ใช้ GCC 8.3.0 หรือ 8.2.1 ที่มีระดับการเพิ่มประสิทธิภาพใด ๆ ยกเว้น-O0ผลผลิตนี้เมื่อผมคาดหวังว่า0 2 2 2คอมไพเลอร์ตัดสินใจว่าสิ่งstrlenนั้นถูก จำกัด ขอบเขตb[0]และดังนั้นจึงไม่สามารถเท่ากับหรือมากกว่าค่าที่ถูกหารด้วย

นี่เป็นข้อบกพร่องในรหัสของฉันหรือเป็นข้อผิดพลาดในคอมไพเลอร์หรือไม่?

สิ่งนี้ไม่ได้สะกดออกมาในมาตรฐานอย่างชัดเจน แต่ฉันคิดว่าการตีความกระแสหลักของตัวชี้ที่มานั้นสำหรับวัตถุใด ๆXรหัส(char *)&Xควรสร้างตัวชี้ที่สามารถวนซ้ำไปมาได้ทั้งหมดX- แนวคิดนี้ควรรักษาไว้แม้ว่าจะXเกิดขึ้น sub-arrays เป็นโครงสร้างภายใน

(คำถามโบนัสมีการตั้งค่าสถานะ gcc เพื่อปิดการเพิ่มประสิทธิภาพเฉพาะนี้หรือไม่)



4
Ref: gcc ของฉัน 7.4.0 รายงาน2 2ภายใต้ตัวเลือกต่าง ๆ
chux - Reinstate Monica

2
@Ale มาตรฐานรับประกันว่าพวกเขาอยู่ในที่อยู่เดียวกัน (struct ไม่สามารถมีช่องว่างภายในเริ่มต้น)
MM MM

3
@ DavidRankin-ReinstateMonica "ส่งผลให้ขอบเขตของถ่าน (*) [8] ถูก จำกัด อยู่ที่ b [0] แต่นั่นเท่าที่ฉันได้รับ" ฉันคิดว่ามันเล็บ เนื่องจากs.bถูก จำกัด ไว้ที่b[0]มันถูก จำกัด ไว้ที่ 8 ตัวอักษรและด้วยเหตุนี้สองตัวเลือก: (1) การเข้าถึงนอกขอบเขตในกรณีที่มี 8 ตัวอักษรที่ไม่ใช่โมฆะซึ่งเป็น UB, (2) มีตัวละครที่เป็นโมฆะ len น้อยกว่า 8 ดังนั้นการหารด้วย 8 จะให้ศูนย์ ดังนั้นการรวบรวมคอมไพเลอร์ (1) + (2) สามารถใช้ UB เพื่อให้ผลลัพธ์เดียวกันกับทั้งสองกรณี
user2162550

3
เมื่อพิจารณาว่า & s == & s.b ไม่มีวิธีใดที่ผลลัพธ์จะแตกต่างกัน ดังที่ @ user2162550 แสดงให้เห็นว่า strlen () ไม่ได้ถูกเรียกและคอมไพเลอร์โยนการคาดเดาว่าผลลัพธ์ของมันจะเป็นเช่นไรแม้ในกรณีที่godbolt.org/z/dMcrdyซึ่งผู้แปลไม่สามารถรู้ได้ มันเป็นปัญหาที่คอมไพเลอร์
Ale

คำตอบ:


-1

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

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

ในรหัสข้างต้นs.bเป็นอาร์เรย์รายการ 23 ของอาร์เรย์ 8 ตัวอักษร เมื่อคุณอ้างถึงเพียงแค่s.bคุณได้รับที่อยู่ของรายการแรกในอาร์เรย์ 23 ไบต์ (และไบต์แรกในอาร์เรย์ 8 อักขระ) เมื่อรหัสแจ้งว่า&s.bจะมีการขอที่อยู่ของที่อยู่ของอาร์เรย์ strlenภายใต้ครอบคลุมคอมไพเลอร์เป็นมากกว่าโอกาสสร้างจัดเก็บในท้องถิ่นบางส่วนการจัดเก็บที่อยู่ของอาร์เรย์ในการมีและการจัดหาที่อยู่ในการจัดเก็บในท้องถิ่นที่จะ

คุณมีทางออกที่เป็นไปได้ 2 ข้อ พวกเขาคือ:

    n = strlen((char *)s.b) / sizeof(BUF);
    printf("%d\n", n);

หรือ

    n = strlen((char *)&s.b[0]) / sizeof(BUF);
    printf("%d\n", n);

ฉันยังพยายามรันโปรแกรมของคุณและแสดงให้เห็นถึงปัญหา แต่เสียงดังกราวและ gcc รุ่นที่ฉันมีกับ-Oตัวเลือกใด ๆยังคงทำงานตามที่คุณคาดไว้ สำหรับสิ่งที่คุ้มค่าฉันกำลังเรียกใช้เสียงดังกราวเวอร์ชัน 9.0.0-2 และ gcc เวอร์ชัน 9.2.1 บน x86_64-pc-linux-gnu)


-2

มีข้อผิดพลาดในรหัส

 memcpy(&s, "1234567812345678", 17);

เช่นมีความเสี่ยงแม้ว่า s เริ่มต้นด้วย b ควรเป็น:

 memcpy(&s.b, "1234567812345678", 17);

strlen ที่สอง () มีข้อผิดพลาดเช่นกัน

n = strlen((char *)&s) / sizeof(BUF);

ตัวอย่างเช่นควรเป็น:

n = strlen((char *)&s.b) / sizeof(BUF);

สตริง sb หากคัดลอกอย่างถูกต้องควรมีความยาว 17 ตัวอักษร ไม่แน่ใจว่าจัดโครงสร้างอย่างไรในหน่วยความจำหากจัดเรียงไว้ คุณได้ตรวจสอบแล้วว่า sb มีอักขระ 17 ตัวที่คัดลอกจริง

ดังนั้น strlen (sb) ควรแสดง 17

printf แสดงหมายเลขจำนวนเต็มเท่านั้นเนื่องจาก% d เป็นจำนวนเต็มและตัวแปร n ถูกประกาศให้เป็นจำนวนเต็ม sizeof (BUF) ควรเป็น 8

ดังนั้น 17 หารด้วย 8 (17/8) ควรพิมพ์ 2 ตามที่ n ประกาศเป็นจำนวนเต็ม เมื่อ memcpy ถูกใช้เพื่อคัดลอกข้อมูลไปยัง s และไม่ใช่ sb ฉันจะเดาว่าเนื่องจากการจัดเรียงหน่วยความจำ สมมติว่ามันเป็นคอมพิวเตอร์ 64 บิตมากกว่าสามารถมีได้ 8 ตัวอักษรในที่อยู่หน่วยความจำเดียว

ตัวอย่างเช่นสมมติว่ามีคนเรียก malloc (1) มากกว่า "พื้นที่ว่าง" ถัดไปที่ไม่จัดแนว ...

การเรียก strlen ครั้งที่สองแสดงหมายเลขที่ถูกต้องเนื่องจากสำเนาสตริงถูกดำเนินการกับ s struct แทนที่จะเป็น sb

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