อะไรคือสิ่งที่ทำให้ฟังก์ชั่นคงที่ใน C?
อะไรคือสิ่งที่ทำให้ฟังก์ชั่นคงที่ใน C?
คำตอบ:
ทำให้ฟังก์ชั่นstatic
หนังได้จากหน่วยแปลอื่น ๆ ซึ่งจะช่วยให้การห่อหุ้ม
helper_file.c
int f1(int); /* prototype */
static int f2(int); /* prototype */
int f1(int foo) {
return f2(foo); /* ok, f2 is in the same translation unit */
/* (basically same .c file) as f1 */
}
int f2(int foo) {
return 42 + foo;
}
main.c :
int f1(int); /* prototype */
int f2(int); /* prototype */
int main(void) {
f1(10); /* ok, f1 is visible to the linker */
f2(12); /* nope, f2 is not visible to the linker */
return 0;
}
#include <helper_file.c>
ไหม ฉันคิดว่านั่นจะทำให้เป็นหน่วยการแปลเดียวในตอนนั้น ...
gcc -std=c99 -pedantic -Wall -Wextra main.c helper_file.c
ในบรรทัดคำสั่งเช่นนี้ ต้นแบบสำหรับฟังก์ชั่นมีอยู่ในไฟล์ต้นฉบับทั้งสอง (ไม่จำเป็นต้องใช้ไฟล์ส่วนหัว) ลิงเกอร์จะแก้ปัญหาฟังก์ชั่น
pmgเป็นจุดที่เกี่ยวกับการห่อหุ้ม; นอกเหนือจากการซ่อนฟังก์ชันจากหน่วยการแปลอื่น (หรือมากกว่านั้นเพราะการทำให้ฟังก์ชั่นstatic
ยังสามารถมอบผลประโยชน์ด้านประสิทธิภาพเมื่อมีการคอมไพเลอร์ปรับให้เหมาะสม
เนื่องจากstatic
ไม่สามารถเรียกใช้ฟังก์ชันจากที่ใดก็ได้นอกหน่วยการแปลปัจจุบัน (ยกเว้นว่าโค้ดจะใช้ตัวชี้ไปยังที่อยู่) คอมไพเลอร์จะควบคุมจุดโทรทั้งหมดในนั้น
ซึ่งหมายความว่าสามารถใช้ ABI ที่ไม่ได้มาตรฐานแบบอินไลน์ได้ทั้งหมดหรือดำเนินการเพิ่มประสิทธิภาพอื่น ๆ ที่อาจไม่สามารถใช้ได้กับฟังก์ชันที่มีการเชื่อมโยงภายนอก
static
ฟังก์ชันหนีหน่วยการแปลปัจจุบันฟังก์ชันนั้นอาจถูกเรียกจากหน่วยการแปลอื่นโดยตรง
static
คำหลักใน C จะใช้ในการรวบรวมไฟล์ (.c เมื่อเทียบกับ .h) เพื่อให้การทำงานมีอยู่เฉพาะในแฟ้มที่
โดยปกติเมื่อคุณสร้างฟังก์ชันคอมไพเลอร์จะสร้าง cruft ที่ linker สามารถใช้เชื่อมโยงการเรียกฟังก์ชันไปยังฟังก์ชันนั้นได้ หากคุณใช้คำสำคัญคงที่ฟังก์ชั่นอื่น ๆ ในไฟล์เดียวกันสามารถเรียกใช้ฟังก์ชั่นนี้ (เพราะมันสามารถทำได้โดยไม่ต้องหันไปหาลิงเกอร์) ในขณะที่ลิงเกอร์ไม่มีข้อมูลที่ให้ไฟล์อื่นเข้าถึงฟังก์ชั่น
มองที่โพสต์ด้านบนฉันต้องการจะชี้รายละเอียด
สมมติว่าไฟล์หลักของเรา ("main.c") มีลักษณะดังนี้:
#include "header.h"
int main(void) {
FunctionInHeader();
}
พิจารณาสามกรณี:
กรณีที่ 1: ไฟล์ส่วนหัวของเรา ("header.h") มีลักษณะดังนี้:
#include <stdio.h>
static void FunctionInHeader();
void FunctionInHeader() {
printf("Calling function inside header\n");
}
จากนั้นคำสั่งต่อไปนี้บน linux:
gcc main.c header.h -o main
จะประสบความสำเร็จ ! ติดตามว่าถ้าใครวิ่ง
./main
ผลลัพธ์จะเป็น
ฟังก์ชั่นการโทรภายในส่วนหัว
ฟังก์ชั่นคงที่นั้นควรพิมพ์อะไร
กรณีที่ 2: ไฟล์ส่วนหัวของเรา ("header.h") มีลักษณะดังนี้:
static void FunctionInHeader();
และเรายังมีไฟล์ "header.c" อีกหนึ่งไฟล์ซึ่งมีลักษณะดังนี้:
#include <stdio.h>
#include "header.h"
void FunctionInHeader() {
printf("Calling function inside header\n");
}
จากนั้นคำสั่งดังต่อไปนี้
gcc main.c header.h header.c -o main
จะให้ข้อผิดพลาด
กรณีที่ 3:
คล้ายกับกรณีที่ 2 ยกเว้นว่าตอนนี้ไฟล์ส่วนหัวของเรา ("header.h") คือ:
void FunctionInHeader(); // keyword static removed
จากนั้นคำสั่งเดียวกับในกรณีที่ 2 จะประสบความสำเร็จและการดำเนินการต่อ /. หลักจะให้ผลลัพธ์ที่คาดหวัง
ดังนั้นจากการทดสอบเหล่านี้ (ดำเนินการบนเครื่อง Acer x86, Ubuntu OS) ฉันตั้งสมมติฐานว่า
คำหลักคงที่ป้องกันฟังก์ชั่นที่จะเรียกในไฟล์ * .c อื่นกว่าที่มันถูกกำหนดไว้
ถูกต้องฉันถ้าฉันผิด
โปรแกรมเมอร์ C ใช้แอตทริบิวต์แบบคงที่เพื่อซ่อนการประกาศตัวแปรและฟังก์ชั่นภายในโมดูลมากเท่าที่คุณจะใช้การประกาศสาธารณะและส่วนตัวใน Java และ C ++ ไฟล์ต้นฉบับ C มีบทบาทเป็นโมดูล ตัวแปรหรือฟังก์ชันโกลบอลใด ๆ ที่ประกาศพร้อมกับสแตติกแอ็ตทริบิวต์เป็นไพรเวตกับโมดูลนั้น ในทำนองเดียวกันตัวแปรทั่วโลกหรือฟังก์ชั่นที่ประกาศโดยไม่มีแอตทริบิวต์คงที่เป็นสาธารณะและสามารถเข้าถึงได้โดยโมดูลอื่น ๆ มันเป็นวิธีการเขียนโปรแกรมที่ดีในการปกป้องตัวแปรและฟังก์ชั่นของคุณด้วยคุณลักษณะคงที่ทุกที่ที่เป็นไปได้
คำตอบของ pmg น่าเชื่อถือมาก หากคุณต้องการทราบว่าการประกาศคงที่ทำงานในระดับวัตถุข้อมูลด้านล่างนี้อาจเป็นที่น่าสนใจสำหรับคุณ ฉันใช้โปรแกรมเดิมที่เขียนโดย pmg และคอมไพเลอร์มันเป็นไฟล์. so (shared object)
เนื้อหาต่อไปนี้หลังจากดัมพ์ไฟล์. so ไปเป็นสิ่งที่มนุษย์สามารถอ่านได้
0000000000000675 f1 : ที่อยู่ของฟังก์ชัน f1
000000000000068c f2 : ที่อยู่ของฟังก์ชัน f2 (staticc)
ทราบความแตกต่างในฟังก์ชั่นที่อยู่มันหมายถึงบางสิ่งบางอย่าง สำหรับฟังก์ชั่นที่มีการประกาศด้วยที่อยู่ที่แตกต่างกันเป็นอย่างดีสามารถบ่งบอกว่า f2 อยู่ไกลมากหรือในส่วนที่แตกต่างกันของไฟล์วัตถุ
ลิงเกอร์ใช้สิ่งที่เรียกว่า PLT (ตารางการเชื่อมโยงขั้นตอน) และ GOT (ตารางออฟเซ็ตทั่วโลก) เพื่อทำความเข้าใจสัญลักษณ์ที่พวกเขาสามารถเข้าถึงลิงก์ได้
สำหรับตอนนี้คิดว่า GOT และ PLT ผูกที่อยู่ทั้งหมดอย่างน่าอัศจรรย์และส่วนแบบไดนามิกเก็บข้อมูลของฟังก์ชั่นเหล่านี้ทั้งหมดที่ linker สามารถมองเห็นได้
หลังจากทิ้งส่วนแบบไดนามิกของไฟล์. so เราจะได้รับรายการจำนวนมาก แต่สนใจฟังก์ชั่นf1และf2เท่านั้น
ส่วนแบบไดนามิกถือรายการสำหรับฟังก์ชั่นf1ตามที่อยู่0000000000000675และไม่ใช่สำหรับf2 !
Num: ชื่อขนาดค่าชนิดผูกเข้ากับชื่อ Ndx
9: 0000000000000675 23 FUNC GLOBAL DEFAULT 11 f1
และนั่นมัน! จากนี้ชัดเจนว่าตัวเชื่อมโยงจะไม่ประสบความสำเร็จในการค้นหาฟังก์ชันf2เนื่องจากไม่อยู่ในส่วนแบบไดนามิกของไฟล์. so
เมื่อมีความจำเป็นต้อง จำกัด การเข้าถึงฟังก์ชั่นบางอย่างเราจะใช้คำหลักคงที่ในขณะที่กำหนดและประกาศฟังก์ชั่น
/* file ab.c */
static void function1(void)
{
puts("function1 called");
}
And store the following code in another file ab1.c
/* file ab1.c */
int main(void)
{
function1();
getchar();
return 0;
}
/* in this code, we'll get a "Undefined reference to function1".Because function 1 is declared static in file ab.c and can't be used in ab1.c */