Accessing static Data and Functions in Legacy C
http://www.renaissancesoftware.net/blog/archives/450 It’s a new year; last year was a leap year; so the quadrennial reports of leap year bugs are coming in. Apologies are in the press from Apple, TomTom, and Microsoft. Trains we stopped f
http://www.renaissancesoftware.net/blog/archives/450
It’s a new year; last year was a leap year; so the quadrennial reports of leap year bugs are coming in. Apologies are in the press from Apple, TomTom, and Microsoft. Trains we stopped from running in China. Somehow calling them glitches seems to make it someone else’s fault, something out of their control. How long have leap years been around? Julius Caesar introduced Leap Years in the Roman empire over 2000 years ago. The Gregorian calendar has been around since 1682. This is not a new idea, or a new bug.
I’m going to try to take one excuse away from the programmers that create these bugs by answering a question that comes up all the time, “How do I test static functions in my C code?”
In code developed using TDD, static functions are tested indirectly through the public interface. As I mentioned in a this article TDD is a code rot radar. It helps you see design problems. Needing direct access to hidden functions and data in C is a sign of code rot. It is time to refactor.
But what about existing legacy code that has statics? It is probably way past the time for idealism and time for some pragmatism. In this article and the next, we’ll look at how to get your code
untouched into the test harness and access those pesky <span>static </span>
variables
and functions.
If you don’t mind touching your code, you could change all mentions of static
to STATIC
.
Then using the preprocessor, STATIC
can
he set to static
during
production and to nothing for test, making the names globally accessible. In gcc you would use these command line options
-
For production builds use
-dSTATIC=static
-
For unit test builds use
-dSTATIC
Let’s look at two options that, at least for access to statics, you will not have to touch your code to get it under test. First is <span>#include</span>
-ing
your .c in the test file. In the next article we’ll build a test adapter .c that give access to the hidden parts.
We are going to revisit code that is similar to the original code responsible for the Zune Bug. I rewrote the code to avoid attracting any lawyers but it is structured similarly to the original Zune driver, complete with static data and functions that must be correct for the function to work.
The header file provides a data structure and initialization function for figuring out the information about the date. Here is the header:
typedef struct Date { int daysSince1980; int year; int dayOfYear; int month; int dayOfMonth; int dayOfWeek; } Date; void Date_Init(Date *, int daysSince1980); enum { SUN = 0, MON, TUE, WED, THU, FRI, SAT }; enum { JAN = 0, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
Date_Init
populates
the Date
instance
passed in. Ignoring the fact that I can probably fully test this by rigging the daysSince1980
,
and inspecting the contents of the resultingDate
structure,
we are going to see how we can directly test the hidden functions and data.
Date_Init
has
three hidden helper functions.
void Date_Init(Date* date, int daysSince1980) { date->daysSince1980 = daysSince1980; FirstSetYearAndDayOfYear(date); ThenSetMonthAndDayOfMonth(date); FinallySetDayOfWeek(date); }
Date_Init
is
the tip of the iceberg. All the interesting stuff is happening in the hidden data and functions:
#include "Date.h" #includeenum { STARTING_YEAR = 1980, STARTING_WEEKDAY = TUE }; static const int nonLeapYearDaysPerMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static const int leapYearDaysPerMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static bool IsLeapYear(int year) { if (year % 400 == 0) return true; if (year % 100 == 0) return false; if (year % 4 == 0) return true; return false; } static int GetDaysInYear(int year) { if (IsLeapYear(year)) return 366; else return 365; } static void FirstSetYearAndDayOfYear(Date * date) { int days = date->daysSince1980; int year = STARTING_YEAR; int daysInYear = GetDaysInYear(year); while (days > daysInYear) { year++; days -= daysInYear; daysInYear = GetDaysInYear(year); } date->dayOfYear = days; date->year = year; } static void ThenSetMonthAndDayOfMonth(Date * date) { int month = 0; int days = date->dayOfYear; const int * daysPerMonth = nonLeapYearDaysPerMonth; if (IsLeapYear(date->year)) daysPerMonth = leapYearDaysPerMonth; while (days > daysPerMonth[month]) { days -= daysPerMonth[month]; month++; } date->month = month + 1; date->dayOfMonth = days; } static void FinallySetDayOfWeek(Date * date) { date->dayOfWeek =((date->daysSince1980-1)+STARTING_WEEKDAY)%7; } void Date_Init(Date* date, int daysSince1980) { date->daysSince1980 = daysSince1980; FirstSetYearAndDayOfYear(date); ThenSetMonthAndDayOfMonth(date); FinallySetDayOfWeek(date); }
Let’s say you want to check the days per month vectors. You might want to write a test to check the months against the handy poem we use here in the US: Thirty days, has September, April, June and November; all the rest have thirty-one, except for February. It has 28 except in leap year it has 29.
If you started by writing this test…
TEST(Date, sept_has_30_days) { LONGS_EQUAL(30, nonLeapYearDaysPerMonth[SEP]); }
… you get this error:
DateTest.cpp:41: error: 'nonLeapYearDaysPerMonth' was not declared in this scope
Let’s get access to the hidden statics in the test case by including Date.c
instead
ofDate.h
in DateTest.cpp
.
The full test case file looks like this now:
#include "CppUTest/TestHarness.h" extern "C" { #include "Date.c" } TEST_GROUP(Date) { }; TEST(Date, sept_has_30_days) { LONGS_EQUAL(30, nonLeapYearDaysPerMonth[SEP]); }
With a little refactoring days per month vectors can be checked like this:
#include "CppUTest/TestHarness.h" extern "C" { #include "Date.c" } TEST_GROUP(Date) { const int * daysPerYearVector; void setup() { daysPerYearVector = nonLeapYearDaysPerMonth; } void itsLeapYear() { daysPerYearVector = leapYearDaysPerMonth; } void CheckNumberOfDaysPerMonth(int month, int days) { LONGS_EQUAL(days, daysPerYearVector[month]); } void ThirtyDaysHasSeptEtc() { CheckNumberOfDaysPerMonth(SEP, 30); CheckNumberOfDaysPerMonth(APR, 30); CheckNumberOfDaysPerMonth(JUN, 30); CheckNumberOfDaysPerMonth(NOV, 30); CheckNumberOfDaysPerMonth(OCT, 31); CheckNumberOfDaysPerMonth(DEC, 31); CheckNumberOfDaysPerMonth(JAN, 31); CheckNumberOfDaysPerMonth(MAR, 31); CheckNumberOfDaysPerMonth(MAY, 31); CheckNumberOfDaysPerMonth(JUL, 31); CheckNumberOfDaysPerMonth(AUG, 31); } void ExceptFebruaryHas(int days) { CheckNumberOfDaysPerMonth(FEB, days); } }; TEST(Date, non_leap_year_day_per_month_table) { ThirtyDaysHasSeptEtc(); ExceptFebruaryHas(28); } TEST(Date, leap_year_day_per_month_table) { itsLeapYear(); ThirtyDaysHasSeptEtc(); ExceptFebruaryHas(28); }
You have access to all the hidden stuff, so you can write the test for the static functions:
IsLeapYear()
, GetDaysInYear()
, FirstSetYearAndDayOfYear()
,ThenSetMonthAndDayOfMonth()
,
and FinallySetDayOfWeek()
.
If Date
been
an abstract data type, with its data structure hidden in the .c
file,
the tests would all have access to structure members hidden from the rest of the world.
There is a downside to this approach, which probably does not matter in this case, but could in others. You can only have one test file that can include a given .c
file.
In the next article we’ll solve that problem.
Have you heard of any interesting leap year bugs? Did you prevent your own?

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제









C 언어에서 정적의 역할 및 사용법: 1. 변수 범위, 3. 내부 함수, 5. 함수 수정, 1. 변수 범위, 변수 앞에 static 키워드가 있는 경우 변수의 범위는 변수가 선언된 파일로 제한됩니다. 즉, 변수는 "파일 수준 범위"이므로 " 중복 정의" 변수 문제 2. 수명주기, 정적 변수는 프로그램 실행을 시작할 때 한 번 초기화되고 프로그램이 끝나면 소멸됩니다.

1. static 먼저 다음 프로그램을 살펴보십시오. publicclassHello{publicstaticvoidmain(String[]args){//(1)System.out.println("Hello, world!");//(2)}} 이것을 보았습니다. 세그먼트 프로그램은 Java를 공부한 대부분의 사람들에게 친숙합니다. Java를 배우지 않았지만 C 등 다른 고급 언어를 배웠더라도 이 코드의 의미를 이해할 수 있어야 합니다. 단순히 "Hello, world"를 출력하고 다른 용도는 없습니다. 그러나 정적 키워드의 주요 목적을 보여줍니다.

데이터 폴더에는 소프트웨어 설정 및 설치 패키지와 같은 시스템 및 프로그램 데이터가 포함되어 있습니다. 데이터 폴더의 각 폴더는 데이터 파일이 파일 이름인 Data 또는 명명된 데이터를 참조하는지 여부에 관계없이 다양한 유형의 데이터 저장 폴더를 나타냅니다. , 모두 시스템이나 프로그램에 의해 사용자 정의된 데이터 파일입니다. 데이터는 데이터 저장을 위한 백업 파일입니다. 일반적으로 meidaplayer, 메모장 또는 워드로 열 수 있습니다.

C 언어 static 키워드의 실제 응용 시나리오 및 활용 기술 1. 개요 static은 C 언어에서 변수와 함수를 수정하는 데 사용되는 키워드입니다. 그 기능은 프로그램 실행 중에 수명 주기와 가시성을 변경하여 변수와 함수를 정적으로 만드는 것입니다. 이 기사에서는 static 키워드의 실제 응용 시나리오와 사용 기술을 소개하고 구체적인 코드 예제를 통해 설명합니다. 2. 정적 변수는 변수의 수명 주기를 연장합니다. static 키워드를 사용하여 지역 변수를 수정하면 수명 주기가 연장됩니다.

정적 기능: 1. 변수 3. 클래스 4. 기타 용도 6. 싱글톤 모드 9. 로컬 변수 메모리 레이아웃 최적화; 11. 반복적인 초기화를 피하십시오. 12. 함수에 사용하십시오. 자세한 소개: 1. 변수, 정적 변수. 변수가 정적으로 선언되면 인스턴스 수준이 아닌 클래스 수준에 속합니다. 즉, 개체 수에 관계없이 정적 변수는 하나만 존재하며 모든 개체가 존재합니다. 이 정적 변수 등을 공유하십시오.

잘못된 mysql 로드 데이터에 대한 해결 방법: 1. 잘못된 문자가 있는 SQL 문을 찾습니다. 2. 문을 "LOAD DATA LOCAL INFILE "employee.txt" INTO TABLE EMPLOYEE 문자 집합 utf8;"으로 수정합니다.

Modifier abstract (추상) 1. Abstract는 클래스를 수정할 수 있습니다. (1) abstract로 수정한 클래스를 추상 클래스라고 합니다. (2) 문법: abstractclass 클래스 이름 {} (3) 특징: 추상 클래스는 객체를 별도로 생성할 수는 없지만 객체를 생성할 수는 있습니다. 선언됨 추상 클래스 클래스 이름 참조 이름; (4) 추상 클래스는 멤버 변수 및 멤버 메소드를 정의할 수 있습니다. (5) 추상 클래스에는 생성자가 있습니다. 하위 클래스 객체를 생성하는 데 사용되면 jvm은 기본적으로 상위 클래스 객체를 생성합니다. 적용 jvm이 상위 클래스 객체를 생성할 때 적용됩니다. 2. Abstract는 메서드를 수정할 수 있습니다. (1) asbtract로 수정된 메서드를 추상 메서드라고 합니다. (2) 구문: 액세스 한정자 추상 반환 값

SQLAND&OR 연산자AND 및 OR 연산자는 둘 이상의 조건을 기반으로 레코드를 필터링하는 데 사용됩니다. AND 및 OR은 WHERE 하위 명령문에서 두 개 이상의 조건을 결합합니다. AND 연산자는 첫 번째 조건과 두 번째 조건이 모두 true인 경우 레코드를 표시합니다. OR 연산자는 첫 번째 조건이나 두 번째 조건 중 하나가 true인 경우 레코드를 표시합니다. "Persons" 테이블: LastNameFirstNameAddressCityAdamsJohnOxfordStreetLondonBushGeorgeFifthAvenueNewYorkCarter
